home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / share / perl5 / Foomatic / DB.pm next >
Text File  |  2009-08-19  |  193KB  |  6,345 lines

  1.  
  2. package Foomatic::DB;
  3. use Exporter;
  4. use Encode;
  5. @ISA = qw(Exporter);
  6.  
  7. @EXPORT_OK = qw(normalizename comment_filter
  8.         get_overview
  9.         getexecdocs
  10.         translate_printer_id
  11.         );
  12. @EXPORT = qw(ppdtoperl ppdfromvartoperl);
  13.  
  14. use Foomatic::Defaults qw(:DEFAULT $DEBUG);
  15. use Data::Dumper;
  16. use POSIX;                      # for rounding integers
  17. use strict;
  18.  
  19. my $ver = '$Revision$ ';
  20.  
  21. # constructor for Foomatic::DB
  22. sub new {
  23.     my $type = shift(@_);
  24.     my $this = bless {@_}, $type;
  25.     $this->{'language'} = "C";
  26.     return $this;
  27. }
  28.  
  29. # A map from the database's internal one-letter driver types to English
  30. my %driver_types = ('F' => 'Filter',
  31.             'P' => 'Postscript',
  32.             'U' => 'Ghostscript Uniprint',
  33.             'G' => 'Ghostscript');
  34.  
  35. # Translate old numerical PostGreSQL printer IDs to the new clear text ones.
  36. sub translate_printer_id {
  37.     my ($oldid) = @_;
  38.     # Read translation table for the printer IDs
  39.     my $translation_table = "$libdir/db/oldprinterids";
  40.     open TRTAB, "< $translation_table" or return $oldid;
  41.     while (<TRTAB>) {
  42.     chomp;
  43.     my $searcholdid = quotemeta($oldid);
  44.     if (/^\s*$searcholdid\s+(\S+)\s*$/) {
  45.         # ID found, return new ID
  46.         my $newid = $1;
  47.         close TRTAB;
  48.         return $newid;
  49.     }
  50.     }
  51.     # ID not found, return original one
  52.     close TRTAB;
  53.     return $oldid;
  54. }
  55.  
  56. # Set language for localized answers
  57. sub set_language {
  58.     my ($this, $language) = @_;
  59.     $this->{'language'} = $language;
  60. }
  61.  
  62. # List of driver names
  63. sub get_driverlist {
  64.     my ($this) = @_;
  65.     return $this->_get_xml_filelist('source/driver');
  66. }
  67.  
  68. # List of printer id's
  69. sub get_printerlist {
  70.     my ($this) = @_;
  71.     return $this->_get_xml_filelist('source/printer');
  72. }
  73.  
  74. sub get_overview {
  75.     my ($this, $rebuild, $cupsppds) = @_;
  76.  
  77.     # In-memory cache only for this process
  78.     return $this->{'overview'} if defined($this->{'overview'}) &&
  79.     !$rebuild;
  80.     $this->{'overview'} = undef;
  81.  
  82.     # Read on-disk cache file if we have one
  83.     if (defined($this->{'overviewfile'})) {
  84.         if (!$rebuild && (-r $this->{'overviewfile'})) {
  85.         if (open CFILE, "< $this->{'overviewfile'}") {
  86.         my $output = join('', <CFILE>);
  87.         close CFILE;
  88.         # Only output the cashed page if it was really
  89.         # completely written Before introduction of this
  90.         # measure pages would not display due to an incomplete
  91.         # cache file until the next page rebuild (or until
  92.         # manually nuking the cache).
  93.         if ($output =~ m!\]\;\s*$!s) {
  94.             my $VAR1;
  95.             if (eval $output) {
  96.             $this->{'overview'} = $VAR1;
  97.             return $this->{'overview'};
  98.             }
  99.         }
  100.         }
  101.     }
  102.     }
  103.  
  104.     # Build a new overview
  105.     my $otype = ($cupsppds ? '-C' : '-O');
  106.     $otype .= ' -n' if ($cupsppds == 1);
  107.     # Generate overview Perl data structure from database
  108.     my $VAR1;
  109.     eval `$bindir/foomatic-combo-xml $otype -l '$libdir' | $bindir/foomatic-perl-data -O -l $this->{'language'}` ||
  110.     die ("Could not run \"foomatic-combo-xml\"/\"foomatic-perl-data\"!");
  111.     $this->{'overview'} = $VAR1;
  112.  
  113.     # Write on-disk cache file if we have one
  114.     if (defined($this->{'overviewfile'})) {
  115.     if (open CFILE, "> $this->{'overviewfile'}") {
  116.         print CFILE Dumper($this->{'overview'});
  117.         close CFILE;
  118.     }
  119.     }
  120.  
  121.     return $this->{'overview'};
  122. }
  123.  
  124. sub get_overview_xml {
  125.     my ($this, $compile) = @_;
  126.  
  127.     open( FCX, "$bindir/foomatic-combo-xml -O -l '$libdir'|")
  128.     or die "Can't execute $bindir/foomatic-combo-xml -O -l '$libdir'";
  129.     $_ = join('', <FCX>);
  130.     close FCX;
  131.     return $_;
  132. }
  133.  
  134. sub get_combo_data_xml {
  135.     my ($this, $drv, $poid, $withoptions) = @_;
  136.  
  137.     # Insert the default option settings if there are some and the user
  138.     # desires it.
  139.     my $options = "";
  140.     if (($withoptions) && (defined($this->{'dat'}))) {
  141.     my $dat = $this->{'dat'};
  142.     for my $arg (@{$dat->{'args'}}) {
  143.         my $name = $arg->{'name'};
  144.         my $default = $arg->{'default'};
  145.         if (($name) && ($default)) {
  146.         $options .= " -o '$name'='$default'";
  147.         }
  148.     }
  149.     }
  150.  
  151.     open( FCX, "$bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'|")
  152.     or die "Can't execute $bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'";
  153.     $_ = join('', <FCX>);
  154.     close FCX;
  155.     return $_;
  156. }
  157.  
  158. sub get_printer {
  159.     my ($this, $poid) = @_;
  160.     # Generate printer Perl data structure from database
  161.     my $VAR1;
  162.     if (-r "$libdir/db/source/printer/$poid.xml") {
  163.     eval (`$bindir/foomatic-perl-data -P -l $this->{'language'} '$libdir/db/source/printer/$poid.xml'`) ||
  164.         die ("Could not run \"foomatic-perl-data\"!");
  165.     } else {
  166.     my ($make, $model);
  167.     if ($poid =~ /^([^\-]+)\-(.*)$/) {
  168.         $make = $1;
  169.         $model = $2;
  170.         $make =~ s/_/ /g;
  171.         $model =~ s/_/ /g;
  172.     } else {
  173.         $make = $poid;
  174.         $make =~ s/_/ /g;
  175.         $model = "Unknown model";
  176.     }
  177.     $VAR1 = {
  178.         'id' => $poid,
  179.         'make' => $make,
  180.         'model' => $model,
  181.         'noxmlentry' => 1
  182.     }
  183.     }
  184.     return $VAR1;
  185. }
  186.  
  187. sub printer_exists {
  188.     my ($this, $poid) = @_;
  189.     # Check whether a printer XML file exists in the database
  190.     return 1 if (-r "$libdir/db/source/printer/$poid.xml");
  191.     return undef;
  192. }
  193.  
  194. sub get_printer_xml {
  195.     my ($this, $poid) = @_;
  196.     return $this->_get_object_xml("source/printer/$poid", 1);
  197. }
  198.  
  199. sub get_driver {
  200.     my ($this, $drv) = @_;
  201.     # Generate driver Perl data structure from database
  202.     my $VAR1;
  203.     if (-r "$libdir/db/source/driver/$drv.xml") {
  204.     eval (`$bindir/foomatic-perl-data -D -l $this->{'language'} '$libdir/db/source/driver/$drv.xml'`) ||
  205.         die ("Could not run \"foomatic-perl-data\"!");
  206.     } else {
  207.     return undef;
  208.     }
  209.     return $VAR1;
  210. }
  211.  
  212. sub get_driver_xml {
  213.     my ($this, $drv) = @_;
  214.     return $this->_get_object_xml("source/driver/$drv", 1);
  215. }
  216.  
  217. # Utility query function sorts of things:
  218.  
  219. sub get_printers_for_driver {
  220.     my ($this, $drv) = @_;
  221.  
  222.     my @printerlist = ();
  223.  
  224.     #my $driver = $this->get_driver($drv);
  225.     #if (defined($driver)) {
  226.     #@printerlist = map { $_->{'id'} } @{$driver->{'printers'}};
  227.     #}
  228.  
  229.     $this->get_overview();
  230.     for my $p (@{$this->{'overview'}}) {
  231.     if (member($drv, @{$p->{'drivers'}})) {
  232.         push(@printerlist, $p->{'id'});
  233.     }
  234.     }
  235.  
  236.     return @printerlist;
  237. }
  238.  
  239. # Routine lookup; just examine the overview
  240. sub get_drivers_for_printer {
  241.     my ($this, $printer) = @_;
  242.  
  243.     my @drivers = ();
  244.  
  245.     my $over = $this->get_overview();
  246.  
  247.     my $p;
  248.     for $p (@{$over}) {
  249.     if ($p->{'id'} eq $printer) {
  250.         return @{$p->{'drivers'}};
  251.     }
  252.     }
  253.  
  254.     return undef;
  255. }
  256.  
  257.  
  258. # Clean some manufacturer's names (for printer search function, taken
  259. # from printerdrake, former printer setup tool of Mandriva Linux)
  260. sub clean_manufacturer_name {
  261.     my ($make) = @_;
  262.     #$make =~ s/^Canon\W.*$/Canon/i;
  263.     #$make =~ s/^Lexmark.*$/Lexmark/i;
  264.     $make =~ s/^Hewlett?[_\s\-]*Packard/HP/i;
  265.     $make =~ s/^Seiko[_\s\-]*Epson/Epson/i;
  266.     $make =~ s/^Kyocera[_\s\-]*Mita/Kyocera/i;
  267.     $make =~ s/^CItoh/C.Itoh/i;
  268.     $make =~ s/^Oki(|[_\s\-]*Data)$/Oki/i;
  269.     $make =~ s/^(SilentWriter2?|ColorMate)/NEC/i;
  270.     $make =~ s/^(XPrint|Majestix)/Xerox/i;
  271.     $make =~ s/^QMS-PS/QMS/i;
  272.     $make =~ s/^konica([_\s\-]|)minolta/KONICA MINOLTA/i;
  273.     $make =~ s/^(Personal|LaserWriter)/Apple/i;
  274.     $make =~ s/^Digital/DEC/i;
  275.     $make =~ s/\s+Inc\.//i;
  276.     $make =~ s/\s+Corp\.//i;
  277.     $make =~ s/\s+SA\.//i;
  278.     $make =~ s/\s+S\.\s*A\.//i;
  279.     $make =~ s/\s+Ltd\.//i;
  280.     $make =~ s/\s+International//i;
  281.     $make =~ s/\s+Int\.//i;
  282.     return $make;
  283. }    
  284.  
  285.  
  286. # Clean some model names (taken from system-config-printer, printer setup
  287. # tool of Fedora/Red Hat, Ubuntu, and Mandriva
  288. sub clean_model_name {
  289.     my ($model) = @_;
  290.     $model =~ s/^Mita[_\s\-]+//i;
  291.     $model =~ s/^AL-(([CM][A-Z]?|)\d+)/AcuLaser $1PS/;
  292.     $model =~ s/\s*\(recommended\)//i;
  293.     $model =~ s/\s*-\s*PostScript\b//i;
  294.     $model =~ s/\s*-\s*BR-Script[123]?\b//i;
  295.     $model =~ s/\s*\bseries\b//i;
  296.     $model =~ s/\s*\bPS[123]?\b//i;
  297.     $model =~ s/\s*PS[123]?$//;
  298.     $model =~ s/\s*\bPXL//i;
  299.     $model =~ s/[\s_-]+BT\b//i;
  300.     $model =~ s/\s*\(Bluetooth\)//i;
  301.     $model =~ s/\s*-\s*(RC|Ver(|sion))\s*-*\s*[0-9\.]+//i;
  302.     $model =~ s/\s*-\s*(RC|Ver(|sion))\b//i;
  303.     $model =~ s/\s*PostScript\s*$//i;
  304.     $model =~ s/\s*BR-Script[123]?\s*$//i;
  305.     $model =~ s/\s*\(\s*\)//i;
  306.     $model =~ s/\s*[\-\/]\s*$//i;
  307.     return $model;
  308. }
  309.  
  310.  
  311. # Guess manufacturer by description with only model name (for printer
  312. # search function, taken from printerdrake, printer setup tool of
  313. # Mandriva Linux)
  314.  
  315. sub guessmake {
  316.  
  317.     my ($description) = @_;
  318.  
  319.     my $manufacturer;
  320.     my $model;
  321.  
  322.     if ($description =~
  323.     /^\s*(DeskJet|LaserJet|OfficeJet|PSC|PhotoSmart)\b/i) {
  324.     # HP printer
  325.     $manufacturer = "HP";
  326.     $model = $description;
  327.     } elsif ($description =~
  328.          /^\s*(Stylus|EPL|AcuLaser)\b/i) {
  329.     # Epson printer
  330.     $manufacturer = "Epson";
  331.     $model = $description;
  332.     } elsif ($description =~
  333.          /^\s*(Aficio)\b/i) {
  334.     # Ricoh printer
  335.     $manufacturer = "Ricoh";
  336.     $model = $description;
  337.     } elsif ($description =~
  338.          /^\s*(Optra|Color\s+JetPrinter)\b/i) {
  339.     # Lexmark printer
  340.     $manufacturer = "Lexmark";
  341.     $model = $description;
  342.     } elsif ($description =~
  343.          /^\s*(imageRunner|Pixma|Pixus|BJC|LBP)\b/i) {
  344.     # Canon printer
  345.     $manufacturer = "Canon";
  346.     $model = $description;
  347.     } elsif ($description =~
  348.          /^\s*(Phaser|DocuPrint|(Work|Document)\s*(Home|)Centre)\b/i) {
  349.     # Xerox printer
  350.     $manufacturer = "Xerox";
  351.     $model = $description;
  352.     } elsif (($description =~ /^\s*(KONICA\s*MINOLTA)\s+(\S.*)$/i) ||
  353.          ($description =~ /^\s*(\S*)\s+(\S.*)$/)) {
  354.     $manufacturer = $1 if $manufacturer eq "";
  355.     $model = $2 if $model eq "";
  356.     }
  357.     return ($manufacturer, $model);
  358. }
  359.  
  360. # Normalize a string, so that for a search only letters
  361. # (case-insensitive), numbers and boundaries between letter blocks and
  362. # number blocks are considered. The pipe '|' as separator between make
  363. # and model is also considered. Blocks of other characters are
  364. # replaced by a single space and boundaries between letters and
  365. # numbers are marked with a single space.
  366. sub normalize {
  367.     my ($str) = @_;
  368.     $str = lc($str);
  369.     $str =~ s/\+/plus/g;
  370.     $str =~ s/[^a-z0-9\|]+/ /g;
  371.     $str =~ s/(?<=[a-z])(?=[0-9])/ /g;
  372.     $str =~ s/(?<=[0-9])(?=[a-z])/ /g;
  373.     $str =~ s/ //g;
  374.     return $str;
  375. }
  376.  
  377. # Find a printer in the database based on an auto-detected device ID
  378. # or a user-typed search term
  379. sub find_printer {
  380.     my ($this, $searchterm, $mode, $output) = @_;
  381.     # $mode = 0: Everything (default)
  382.     # $mode = 1: No matches on only the manufacturer
  383.     # $mode = 2: No matches on only the manufacturer or only the model
  384.     # $mode = 3: Exact matches of device ID, make/model, or Foomatic ID
  385.     #            plus matches of the page description language
  386.     # $mode = 4: Exact matches of device ID, make/model, or Foomatic ID
  387.     #            only
  388.     # $output = 0: Everything
  389.     # $output = 1: Only best match class (default)
  390.     # $output = 2: Only best match
  391.  
  392.     # Correct options
  393.     $mode = 0 if !defined $mode;
  394.     $mode = 0 if $mode < 0;
  395.     $mode = 4 if $mode > 4;
  396.     $output = 1 if !defined $output;
  397.     $output = 0 if $output < 0;
  398.     $output = 2 if $output > 2;
  399.  
  400.     my $over = $this->get_overview();
  401.  
  402.     my %results;
  403.  
  404.     # Parse the search term
  405.     my ($automake, $automodel, $autodescr, $autocmdset, $autosku);
  406.     my $deviceid = 0;
  407.  
  408.     # Do we have a device ID?
  409.     if ($searchterm =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i) {
  410.     $automake = $2;
  411.     $deviceid = 1;
  412.     }
  413.     if ($searchterm =~ /(MDL|MODEL):\s*([^:;]+);?/i) {
  414.     $automodel = $2;
  415.     $automodel =~ s/\s+$//;
  416.     $deviceid = 1;
  417.     }
  418.     if ($searchterm =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i) {
  419.     $autodescr = $2;
  420.     $autodescr =~ s/\s+$//;
  421.     $deviceid = 1;
  422.     }
  423.     if ($searchterm =~ /(CMD|COMMANDS?\s?SET):\s*([^:;]+);?/i) {
  424.     $autocmdset = $2;
  425.     $deviceid = 1;
  426.     }
  427.     if ($searchterm =~ /(SKU):\s*([^:;]+);?/i) {
  428.     $autosku = $2;
  429.     $autosku =~ s/\s+$//;
  430.     $deviceid = 1;
  431.     }
  432.  
  433.     # Search term is not a device ID
  434.     if (!$deviceid) {
  435.     if ($searchterm =~ /^([^\|]+)\|([^\|]+|)(\|.*?|)$/) {
  436.         $automake = $1;
  437.         $automodel = $2;
  438.     } else {
  439.         $autodescr = $searchterm;
  440.     }
  441.     }
  442.  
  443.     # This is the algorithm used in printerdrake (printer setup tool
  444.     # of Mandriva Linux) to match results of the printer auto-detection
  445.     # with the printer database
  446.  
  447.     # Clean some manufacturer's names
  448.     my $descrmake = clean_manufacturer_name($automake);
  449.  
  450.     # Generate data to match human-readable make/model names
  451.     # of Foomatic database
  452.     my $descr;
  453.     if ($automake && $autosku) {
  454.     $descr = "$descrmake|$autosku";
  455.     } elsif ($automake && $automodel) {
  456.     $descr = "$descrmake|$automodel";
  457.     } elsif ($autodescr && (length($autodescr) > 5)) {
  458.     my ($mf, $md) =
  459.         guessmake($autodescr);
  460.     $descrmake = clean_manufacturer_name($mf);
  461.     $descr = "$descrmake|$md";
  462.     } elsif ($automodel) {
  463.     my ($mf, $md) =
  464.         guessmake($automodel);
  465.     $descrmake = clean_manufacturer_name($mf);
  466.     $descr = "$descrmake|$md";
  467.     } elsif ($automake) {
  468.     $descr = "$descrmake|";
  469.     }
  470.  
  471.     # Remove manufacturer's name from the beginning of the
  472.     # description (do not do this with manufacturer names which
  473.     # contain odd characters)
  474.     $descr =~ s/^$descrmake\|\s*$descrmake\s*/$descrmake|/i
  475.     if $descrmake && 
  476.     $descrmake !~ m![\\/\(\)\[\]\|\.\$\@\%\*\?]!;
  477.  
  478.     # Clean up the description from noise which makes the best match
  479.     # difficult
  480.     $descr =~ s/\s+[Ss]eries//i;
  481.     $descr =~ s/\s+\(?[Pp]rinter\)?$//i;
  482.  
  483.     # Try to find an exact match, check both whether the detected
  484.     # make|model is in the make|model of the database entry and vice versa
  485.     # If there is more than one matching database entry, the longest match
  486.     # counts.
  487.     my $matchlength = -1000;
  488.     my $bestmatchlength = -1000;
  489.     my $p;
  490.   DBENTRY: for $p (@{$over}) {
  491.     # Try to match the device ID string of the auto-detection
  492.     if ($p->{make} =~ /Generic/i) {
  493.         # Database entry for generic printer, check printer
  494.         # languages (command set)
  495.         if ($p->{model} =~ m!PCL\s*5/5e!i) {
  496.         # Generic PCL 5/5e Printer
  497.         if ($autocmdset =~
  498.             /(^|[:,])PCL\s*\-*\s*(5|)($|[,;])/i) {
  499.             $matchlength = 70;
  500.             $bestmatchlength = $matchlength if
  501.             $bestmatchlength < $matchlength;
  502.             $results{$p->{id}} = $matchlength if
  503.             (!defined($results{$p->{id}}) ||
  504.              ($results{$p->{id}} < $matchlength));
  505.             next;
  506.         }
  507.         } elsif ($p->{model} =~ m!PCL\s*(6|XL)!i) {
  508.         # Generic PCL 6/XL Printer
  509.         if ($autocmdset =~
  510.             /(^|[:,])PCL\s*\-*\s*(6|XL)($|[,;])/i) {
  511.             $matchlength = 80;
  512.             $bestmatchlength = $matchlength if
  513.             $bestmatchlength < $matchlength;
  514.             $results{$p->{id}} = $matchlength if
  515.             (!defined($results{$p->{id}}) ||
  516.              ($results{$p->{id}} < $matchlength));
  517.             next;
  518.         }
  519.         } elsif ($p->{model} =~ m!(PostScript)!i) {
  520.         # Generic PostScript Printer
  521.         if ($autocmdset =~
  522.             /(^|[:,\s])(PS|POSTSCRIPT)[^:;,]*($|[,;])/i) {
  523.             $matchlength = 90;
  524.             $bestmatchlength = $matchlength if
  525.             $bestmatchlength < $matchlength;
  526.             $results{$p->{id}} = $matchlength if
  527.             (!defined($results{$p->{id}}) ||
  528.              ($results{$p->{id}} < $matchlength));
  529.             next;
  530.         }
  531.         }
  532.  
  533.     } else {
  534.         # "Real" manufacturer, check manufacturer, model, and/or
  535.         # description
  536.         my $matched = 1;
  537.         my ($mfg, $mdl, $des, $sku);
  538.         my $ieee1284 = deviceIDfromDBEntry($p);
  539.         if ($ieee1284 =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i) {
  540.         $mfg = $2;
  541.         }
  542.         if ($ieee1284 =~ /(MDL|MODEL):\s*([^:;]+);?/i) {
  543.         $mdl = $2;
  544.         $mdl =~ s/\s+$//;
  545.         }
  546.         if ($ieee1284 =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i) {
  547.         $des = $2;
  548.         $des =~ s/\s+$//;
  549.         }
  550.         if ($ieee1284 =~ /(SKU):\s*([^:;]+);?/i) {
  551.         $sku = $2;
  552.         $sku =~ s/\s+$//;
  553.         }
  554.         if ($mfg) {
  555.         if ($mfg ne $automake) {
  556.             $matched = 0;
  557.         }
  558.         }
  559.         if ($mdl) {
  560.         if ($mdl ne $automodel) {
  561.             $matched = 0;
  562.         }
  563.         }
  564.         if ($des) {
  565.         if ($des ne $autodescr) {
  566.             $matched = 0;
  567.         }
  568.         }
  569.         if ($sku && $autosku) {
  570.         if ($sku ne $autosku) {
  571.             $matched = 0;
  572.         }
  573.         }
  574.         if ($matched &&
  575.         ($des || ($mfg && ($mdl || ($sku && $autosku))))) {
  576.         # Full match to known auto-detection data
  577.         $matchlength = 1000;
  578.         $bestmatchlength = $matchlength if
  579.             $bestmatchlength < $matchlength;
  580.         $results{$p->{id}} = $matchlength if
  581.                 (!defined($results{$p->{id}}) ||
  582.                  ($results{$p->{id}} < $matchlength)); 
  583.         next;
  584.         }
  585.     }
  586.  
  587.     # Try to match the (human-readable) make and model of the
  588.     # Foomatic database or of the PPD file
  589.     my $dbmakemodel = "$p->{make}|$p->{model}";
  590.  
  591.     # At first try to match make and model, then only model and
  592.     # after that only make
  593.     my $searchtasks = [[$descr, $dbmakemodel, 0],
  594.                [$searchterm, $p->{model}, -200],
  595.                [clean_manufacturer_name($searchterm),
  596.                 $p->{make}, -300],
  597.                [$searchterm, $p->{id}, 0]];
  598.  
  599.     foreach my $task (@{$searchtasks}) {
  600.  
  601.         # Do not try to match search terms or database entries without
  602.         # real content
  603.         next unless $task->[0] =~ /[a-z]/i;
  604.         next unless $task->[1] =~ /[a-z]/i;
  605.  
  606.         # If make and model match exactly, we have found the correct
  607.         # entry and we can stop searching human-readable makes and
  608.         # models
  609.         if (normalize($task->[1]) eq normalize($task->[0])) {
  610.         $matchlength = 100;
  611.         $bestmatchlength = $matchlength + $task->[2] if
  612.             $bestmatchlength < $matchlength + $task->[2];
  613.         $results{$p->{id}} = $matchlength + $task->[2] if
  614.                 (!defined($results{$p->{id}}) ||
  615.                  ($results{$p->{id}} < $matchlength)); 
  616.         next DBENTRY;
  617.         }
  618.  
  619.         # Matching a part of the human-readable makes and models
  620.         # should only be done if the search term is not the name of
  621.         # an old model, otherwise the newest, not yet listed models
  622.         # match with the oldest model of the manufacturer (as the
  623.         # Epson Stylus Photo 900 with the original Epson Stylus Photo)
  624.         my @badsearchterms = 
  625.         ("HP|DeskJet",
  626.          "HP|LaserJet",
  627.          "HP|DesignJet",
  628.          "HP|OfficeJet",
  629.          "HP|PhotoSmart",
  630.          "EPSON|Stylus",
  631.          "EPSON|Stylus Color",
  632.          "EPSON|Stylus Photo",
  633.          "EPSON|Stylus Pro",
  634.          "XEROX|WorkCentre",
  635.          "XEROX|DocuPrint");
  636.         if (!member($task->[0], @badsearchterms)) {
  637.         my $searcht = normalize($task->[0]);
  638.         my $lsearcht = length($searcht);
  639.         $searcht =~ s!([\\/\(\)\[\]\|\.\$\@\%\*\?])!\\$1!g;
  640.         $searcht =~ s!(\\\|)!$1.*!g;
  641.         my $s = normalize($task->[1]);
  642.         if ((1 || $lsearcht >= $matchlength) &&
  643.             $s =~ m!$searcht!i) {
  644.             $matchlength = $lsearcht;
  645.             $bestmatchlength = $matchlength + $task->[2] if
  646.             $bestmatchlength < $matchlength + $task->[2];
  647.             $results{$p->{id}} = $matchlength + $task->[2] if
  648.                 (!defined($results{$p->{id}}) ||
  649.                  ($results{$p->{id}} < $matchlength)); 
  650.         }
  651.         }
  652.         if (!member($task->[1], @badsearchterms)) {
  653.         my $searcht = normalize($task->[1]);
  654.         my $lsearcht = length($searcht);
  655.         $searcht =~ s!([\\/\(\)\[\]\|\.\$\@\%\*\?])!\\$1!g;
  656.         $searcht =~ s!(\\\|)!$1.*!g;
  657.         my $s = normalize($task->[0]);
  658.         if ((1 || $lsearcht >= $matchlength) &&
  659.             $s =~ m!$searcht!i) {
  660.             $matchlength = $lsearcht;
  661.             $bestmatchlength = $matchlength + $task->[2] if
  662.             $bestmatchlength < $matchlength + $task->[2];
  663.             $results{$p->{id}} = $matchlength + $task->[2] if
  664.                 (!defined($results{$p->{id}}) ||
  665.                  ($results{$p->{id}} < $matchlength)); 
  666.         }
  667.         }
  668.     }
  669.     }
  670.  
  671.     return grep {
  672.     ((($mode == 4) && ($results{$_} >= 100)) ||
  673.      (($mode == 3) && ($results{$_} > 60)) ||
  674.      (($mode == 2) && ($results{$_} > -100)) ||
  675.      (($mode == 1) && ($results{$_} > -200)) ||
  676.      ($mode == 0)) &&
  677.     (($output == 0) ||
  678.      (($output == 1) &&
  679.       !((($bestmatchlength >= 100) && ($results{$_} < 100)) || 
  680.         (($bestmatchlength >= 60) && ($results{$_} < 60)) || 
  681.         (($bestmatchlength >= 0) && ($results{$_} < 0)) || 
  682.         (($bestmatchlength >= -100) && ($results{$_} < -100)) || 
  683.         (($bestmatchlength >= -200) && ($results{$_} < -200)) || 
  684.         (($bestmatchlength >= -300) && ($results{$_} < -300)) || 
  685.         (($bestmatchlength >= -400) && ($results{$_} < -400)))) ||
  686.      (($output == 2) &&
  687.       ($results{$_} == $bestmatchlength)))
  688.     } sort { $results{$b} <=> $results{$a} } keys(%results);
  689. }
  690.  
  691. # This function sorts the options at first by their group membership and
  692. # then by their names appearing in the list of functional areas. This way
  693. # it will be made easier to build the PPD file with option groups and in
  694. # user interfaces options will appear sorted by their functionality.
  695. sub sortargs {
  696.  
  697.     # All sorting done case-insensitive and characters which are not a
  698.     # letter or number are taken out!!
  699.  
  700.     # List of typical option names to appear at first
  701.     # The terms must fit to the beginning of the line, terms which must fit
  702.     # exactly must have '\$' in the end.
  703.     my @standardopts = (
  704.             # The most important composite option
  705.             "printoutmode",
  706.             # Options which appear in the "General" group in 
  707.             # CUPS and similar media handling options
  708.             "pagesize",
  709.             "papersize",
  710.             "mediasize",
  711.             "inputslot",
  712.             "papersource",
  713.             "mediasource",
  714.             "sheetfeeder",
  715.             "mediafeed",
  716.             "paperfeed",
  717.             "manualfeed",
  718.             "manual",
  719.             "outputtray",
  720.             "outputslot",
  721.             "outtray",
  722.             "faceup",
  723.             "facedown",
  724.             "mediatype",
  725.             "papertype",
  726.             "mediaweight",
  727.             "paperweight",
  728.             "duplex",
  729.             "sides",
  730.             "binding",
  731.             "tumble",
  732.             "notumble",
  733.             "media",
  734.             "paper",
  735.             # Other hardware options
  736.             "inktype",
  737.             "ink",
  738.             # Page choice/ordering options
  739.             "pageset",
  740.             "pagerange",
  741.             "pages",
  742.             "nup",
  743.             "numberup",
  744.             # Printout quality, colour/bw
  745.             "resolution",
  746.             "gsresolution",
  747.             "hwresolution",
  748.             "jclresolution",
  749.             "fastres",
  750.             "jclfastres",
  751.             "quality",
  752.             "printquality",
  753.             "printingquality",
  754.             "printoutquality",
  755.             "bitsperpixel",
  756.             "econo",
  757.             "jclecono",
  758.             "tonersav",
  759.             "photomode",
  760.             "photo",
  761.             "colormode",
  762.             "colourmode",
  763.             "color",
  764.             "colour",
  765.             "grayscale",
  766.             "gray",
  767.             "monochrome",
  768.             "mono",
  769.             "blackonly",
  770.             "colormodel",
  771.             "colourmodel",
  772.             "processcolormodel",
  773.             "processcolourmodel",
  774.             "printcolors",
  775.             "printcolours",
  776.             "outputtype",
  777.             "outputmode",
  778.             "printingmode",
  779.             "printoutmode",
  780.             "printmode",
  781.             "mode",
  782.             "imagetype",
  783.             "imagemode",
  784.             "image",
  785.             "dithering",
  786.             "dither",
  787.             "halftoning",
  788.             "halftone",
  789.             "floydsteinberg",
  790.             "ret\$",
  791.             "cret\$",
  792.             "photoret\$",
  793.             "smooth",
  794.             # Adjustments
  795.             "gammacorrection",
  796.             "gammacorr",
  797.             "gammageneral",
  798.             "mastergamma",
  799.             "stpgamma",
  800.             "gammablack",
  801.             "blackgamma",
  802.             "gammacyan",
  803.             "cyangamma",
  804.             "gammamagenta",
  805.             "magentagamma",
  806.             "gammayellow",
  807.             "yellowgamma",
  808.             "gammared",
  809.             "redgamma",
  810.             "gammagreen",
  811.             "greengamma",
  812.             "gammablue",
  813.             "bluegamma",
  814.             "gamma",
  815.             "density",
  816.             "stpdensity",
  817.             "hpljdensity",
  818.             "tonerdensity",
  819.             "inkdensity",
  820.             "brightness",
  821.             "stpbrightness",
  822.             "saturation",
  823.             "stpsaturation",
  824.             "hue",
  825.             "stphue",
  826.             "tint",
  827.             "stptint",
  828.             "contrast",
  829.             "stpcontrast",
  830.             "black",
  831.             "stpblack",
  832.             "cyan",
  833.             "stpcyan",
  834.             "magenta",
  835.             "stpmagenta",
  836.             "yellow",
  837.             "stpyellow",
  838.             "red",
  839.             "stpred",
  840.             "green",
  841.             "stpgreen",
  842.             "blue",
  843.             "stpblue"
  844.             );
  845.  
  846.     my @standardgroups = (
  847.               "general",
  848.               "media",
  849.               "quality",
  850.               "imag",
  851.               "color",
  852.               "output",
  853.               "finish",
  854.               "stapl",
  855.               "extra",
  856.               "install"
  857.               );
  858.  
  859.     my $compare;
  860.  
  861.     # Argument records
  862.     my $firstarg = $a;
  863.     my $secondarg = $b;
  864.  
  865.     # Bring the two option names into a standard form to compare them
  866.     # in a better way
  867.     my $first = normalizename(lc($firstarg->{'name'}));
  868.     $first =~ s/[\W_]//g;
  869.     my $second = normalizename(lc($secondarg->{'name'}));
  870.     $second =~ s/[\W_]//g;
  871.  
  872.     # group names
  873.     my $firstgr = $firstarg->{'group'};
  874.     my @firstgroup;
  875.     @firstgroup = split("/", $firstgr) if defined($firstgr); 
  876.     my $secondgr = $secondarg->{'group'};
  877.     my @secondgroup;
  878.     @secondgroup = split("/", $secondgr) if defined($secondgr);
  879.  
  880.     my $i = 0;
  881.  
  882.     # Compare groups
  883.     while ($firstgroup[$i] && $secondgroup[$i]) {
  884.  
  885.     # Normalize group names
  886.     my $firstgr = normalizename(lc($firstgroup[$i]));
  887.     $firstgr =~ s/[\W_]//g;
  888.     my $secondgr = normalizename(lc($secondgroup[$i]));
  889.     $secondgr =~ s/[\W_]//g;
  890.         
  891.     # Are the groups in the list of standard group names?
  892.     my $j;
  893.     for ($j = 0; $j <= $#standardgroups; $j++) {
  894.         my $firstinlist = ($firstgr =~ /^$standardgroups[$j]/);
  895.         my $secondinlist = ($secondgr =~ /^$standardgroups[$j]/);
  896.         if (($firstinlist) && (!$secondinlist)) {return -1};
  897.         if (($secondinlist) && (!$firstinlist)) {return 1};
  898.         if (($firstinlist) && ($secondinlist)) {last};
  899.     }
  900.  
  901.     # Compare normalized group names
  902.     $compare = $firstgr cmp $secondgr;
  903.     if ($compare != 0) {return $compare};
  904.  
  905.     # Compare original group names
  906.     $compare = $firstgroup[$i] cmp $secondgroup[$i];
  907.     if ($compare != 0) {return $compare};
  908.     
  909.     $i++;
  910.     }
  911.  
  912.     # The one with a deeper level in the group tree will come later
  913.     if ($firstgroup[$i]) {return 1};
  914.     if ($secondgroup[$i]) {return -1};
  915.  
  916.     # Sort by order parameter if the order parameters are different
  917.     if (defined($firstarg->{'order'}) && defined($secondarg->{'order'}) &&
  918.     $firstarg->{'order'} != $secondarg->{'order'}) {
  919.     return $firstarg->{'order'} cmp $secondarg->{'order'};
  920.     }
  921.  
  922.     # Check whether the argument names are in the @standardopts list
  923.     for ($i = 0; $i <= $#standardopts; $i++) {
  924.     my $firstinlist = ($first =~ /^$standardopts[$i]/);
  925.     my $secondinlist = ($second =~ /^$standardopts[$i]/);
  926.     if (($firstinlist) && (!$secondinlist)) {return -1};
  927.     if (($secondinlist) && (!$firstinlist)) {return 1};
  928.     if (($firstinlist) && ($secondinlist)) {last};
  929.     }
  930.  
  931.     # None of the search terms in the list, compare the standard-formed
  932.     # strings
  933.     $compare = ( $first cmp $second );
  934.     if ($compare != 0) {return $compare};
  935.  
  936.     # No other criteria fullfilled, compare the original input strings
  937.     return $firstarg->{'name'} cmp $secondarg->{'name'};
  938. }
  939.  
  940. sub sortvals {
  941.  
  942.     # All sorting done case-insensitive and characters which are not a letter
  943.     # or number are taken out!!
  944.  
  945.     # List of typical choice names to appear at first
  946.     # The terms must fit to the beginning of the line, terms which must fit
  947.     # exactly must have '\$' in the end.
  948.     my @standardvals = (
  949.             # Default setting
  950.             "default",
  951.             "printerdefault",
  952.             # "Neutral" setting
  953.             "None\$",
  954.             # Paper sizes
  955.             "letter\$",
  956.             #"legal",
  957.             "a4\$",
  958.             # Paper types
  959.             "plain",
  960.             # Printout Modes
  961.             "draft\$",
  962.             "draft\.gray",
  963.             "draft\.mono",
  964.             "draft\.",
  965.             "draft",
  966.             "normal\$",
  967.             "normal\.gray",
  968.             "normal\.mono",
  969.             "normal\.",
  970.             "normal",
  971.             "high\$",
  972.             "high\.gray",
  973.             "high\.mono",
  974.             "high\.",
  975.             "high",
  976.             "veryhigh\$",
  977.             "veryhigh\.gray",
  978.             "veryhigh\.mono",
  979.             "veryhigh\.",
  980.             "veryhigh",
  981.             "photo\$",
  982.             "photo\.gray",
  983.             "photo\.mono",
  984.             "photo\.",
  985.             "photo",
  986.             # Trays
  987.             "upper",
  988.             "top",
  989.             "middle",
  990.             "mid",
  991.             "lower",
  992.             "bottom",
  993.             "highcapacity",
  994.             "multipurpose",
  995.             "tray",
  996.             );
  997.  
  998.     # Do not waste time if the input strings are equal
  999.     if ($a eq $b) {return 0;}
  1000.  
  1001.     # Are the two strings numbers? Compare them numerically
  1002.     if (($a =~ /^[\d\.]+$/) && ($b =~ /^[\d\.]+$/)) {
  1003.     my $compare = ( $a <=> $b );
  1004.     if ($compare != 0) {return $compare};
  1005.     }
  1006.  
  1007.     # Bring the two option names into a standard form to compare them
  1008.     # in a better way
  1009.     my $first = lc($a);
  1010.     $first =~ s/[\W_]//g;
  1011.     my $second = lc($b);
  1012.     $second =~ s/[\W_]//g;
  1013.  
  1014.     # Check whether they are in the @standardvals list
  1015.     for (my $i = 0; $i <= $#standardvals; $i++) {
  1016.     my $firstinlist = ($first =~ /^$standardvals[$i]/);
  1017.     my $secondinlist = ($second =~ /^$standardvals[$i]/);
  1018.     if (($firstinlist) && (!$secondinlist)) {return -1};
  1019.     if (($secondinlist) && (!$firstinlist)) {return 1};
  1020.     if (($firstinlist) && ($secondinlist)) {last};
  1021.     }
  1022.     
  1023.     # None of the search terms in the list, compare the standard-formed 
  1024.     # strings
  1025.     my $compare = ( normalizename($first) cmp normalizename($second) );
  1026.     if ($compare != 0) {return $compare};
  1027.  
  1028.     # No other criteria fullfilled, compare the original input strings
  1029.     return $a cmp $b;
  1030. }
  1031.  
  1032. # Take driver/pid arguments and generate a Perl data structure for the
  1033. # Perl filter scripts. Sort the options and enumerated choices so that
  1034. # they get presented more nicely on frontends which do not sort by
  1035. # themselves
  1036.  
  1037. sub getdat ($ $ $) {
  1038.     my ($this, $drv, $poid) = @_;
  1039.  
  1040.     my $ppdfile;
  1041.  
  1042.     # Do we have a link to a custom PPD file for this driver in the
  1043.     # printer XML file? Then return the custom PPD
  1044.  
  1045.     my $p = $this->get_printer($poid);
  1046.     if (defined($p->{'drivers'})) {
  1047.     for my $d (@{$p->{'drivers'}}) {
  1048.         next if ($d->{'id'} ne $drv);
  1049.         $ppdfile = $d->{'ppd'} if defined($d->{'ppd'});
  1050.         last;
  1051.     }
  1052.     }
  1053.  
  1054.     # Do we have a PostScript printer and a link to a manufacturer-
  1055.     # supplied PPD file? Then return the manufacturer-supplied PPD
  1056.  
  1057.     if ($drv =~ /^Postscript$/i) {
  1058.     $ppdfile = $p->{'ppdurl'} if defined($p->{'ppdurl'});
  1059.     }
  1060.  
  1061.     # There is a link to a custom PPD, if it is installed on the local
  1062.     # machine, use the custom PPD instead of generating one from the
  1063.     # Foomatic data
  1064.     if ($ppdfile) {
  1065.     $ppdfile =~ s,^http://.*/(PPD/.*)$,$1,;
  1066.     $ppdfile = $libdir . "/db/source/" . $ppdfile;
  1067.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  1068.     if (-r $ppdfile) {
  1069.         $this->getdatfromppd($ppdfile);
  1070.         $this->{'dat'}{'ppdfile'} = $ppdfile;
  1071.         return $this->{'dat'};
  1072.     }
  1073.     }
  1074.  
  1075.     # Generate Perl data structure from database
  1076.     my %dat;            # Our purpose in life...
  1077.     my $VAR1;
  1078.     eval (`$bindir/foomatic-combo-xml -d '$drv' -p '$poid' -l '$libdir' | $bindir/foomatic-perl-data -C -l $this->{'language'}`) ||
  1079.     die ("Could not run \"foomatic-combo-xml\"/" .
  1080.          "\"foomatic-perl-data\"!");
  1081.     %dat = %{$VAR1};
  1082.  
  1083.     # Funky one-at-a-time cache thing
  1084.     $this->{'dat'} = \%dat;
  1085.  
  1086.     # We do some additional stuff which is very awkward to implement in C
  1087.     # now, so we do it here
  1088.  
  1089.     # Some clean-up
  1090.     checklongnames($this->{'dat'});
  1091.     sortoptions($this->{'dat'});
  1092.     generalentries($this->{'dat'});
  1093.     if (defined($this->{'dat'}{'shortdescription'})) {
  1094.     $this->{'dat'}{'shortdescription'} =~ s/[\s\n\r]+/ /s;
  1095.     $this->{'dat'}{'shortdescription'} =~ s/^\s+//;
  1096.     $this->{'dat'}{'shortdescription'} =~ s/\s+$//;
  1097.     }
  1098.     return \%dat;
  1099. }
  1100.  
  1101. sub getdatfromppd {
  1102.  
  1103.     my ($this, $ppdfile, $parameters) = @_;
  1104.  
  1105.     my $dat = ppdtoperl($ppdfile, $parameters);
  1106.     
  1107.     if (!defined($dat)) {
  1108.     die ("Unable to open PPD file \'$ppdfile\'\n");
  1109.     }
  1110.  
  1111.     $this->{'dat'} = $dat;
  1112.  
  1113. }
  1114.  
  1115. sub ppdtoperl {
  1116.  
  1117.     # Build a Perl data structure of the printer/driver options
  1118.  
  1119.     my ($ppdfile, $parameters) = @_;
  1120.  
  1121.     # Load the PPD file and send it to the parser
  1122.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  1123.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or return undef;
  1124.     my @ppd = <PPD>;
  1125.     close PPD;
  1126.     $parameters->{'ppdfile'} = $ppdfile if $parameters;
  1127.     return ppdfromvartoperl(\@ppd, $parameters);
  1128. }
  1129.  
  1130. sub apply_driver_and_pdl_info {
  1131.  
  1132.     # Find out printer's page description languages and suitable drivers
  1133.  
  1134.     my ($dat, $parameters) = @_;
  1135.  
  1136.     my %drivers;
  1137.     my $pdls;
  1138.     my $ppddlpath;
  1139.     my $ppddrv = $dat->{'driver'};
  1140.     if ($parameters) {
  1141.     if (defined($parameters->{'drivers'})) {
  1142.         foreach my $d (@{$parameters->{'drivers'}}) {
  1143.         $drivers{$d} = 1;
  1144.         }
  1145.         $ppddrv = $parameters->{'drivers'}[0];
  1146.         $dat->{'driver'} = $parameters->{'drivers'}[0] if
  1147.         $parameters->{'drivers'}[0] =~ /^$dat->{'driver'}/;
  1148.     }
  1149.     if ($parameters->{'recommendeddriver'}) {
  1150.         $dat->{'driver'} = $parameters->{'recommendeddriver'};
  1151.     }
  1152.     if (defined($parameters->{'pdls'})) {
  1153.         $pdls = join(",", @{$parameters->{'pdls'}});
  1154.     }
  1155.     if ($parameters->{'ppdfile'} && $parameters->{'ppdlink'}) {
  1156.         my $ppdfile = $parameters->{'ppdfile'};
  1157.         if ($parameters->{'basedir'}) {
  1158.         my $basedir = $parameters->{'basedir'};
  1159.         $basedir =~ s:/+$::;
  1160.         if (! -d $basedir) {
  1161.             die ("PPD base directory $basedir does not exist!\n");
  1162.         }
  1163.         if (! -r $ppdfile) {
  1164.             $ppddlpath = $ppdfile;
  1165.             $ppdfile = $basedir . "/" . $ppdfile;
  1166.             if (! -r $ppdfile) {
  1167.             die ("Given PPD file not found, neither as $ppddlpath nor as $ppdfile!\n");
  1168.             }
  1169.         } else {
  1170.             $ppdfile =~ m:$basedir/(.*)$:;
  1171.             $ppddlpath = $1;
  1172.         }
  1173.         } else {
  1174.         if (! -r $ppdfile) {
  1175.             die ("Given PPD file $ppdfile not found!\n");
  1176.         }
  1177.         $ppddlpath = $ppdfile;
  1178.         }
  1179.     }
  1180.     }
  1181.                   
  1182.     if ($dat->{'driver'} =~ /Postscript/i) {
  1183.     $pdls = join(',', ($pdls, "POSTSCRIPT$dat->{'ppdpslevel'}"));
  1184.     } elsif ($dat->{'driver'} =~ /(pxl|pcl[\s\-]?xl)/i) {
  1185.     $pdls = join(',', ($pdls, "PCLXL"));
  1186.     } elsif ($dat->{'driver'} =~ /(ljet4|lj4)/i) {
  1187.     $pdls = join(',', ($pdls, "PCL5e"));
  1188.     } elsif (($dat->{'driver'} =~ /clj/i) && $dat->{'color'}) {
  1189.     $pdls = join(',', ($pdls, "PCL5c"));
  1190.     } elsif ($dat->{'driver'} =~ /(ljet3|lj3)/i) {
  1191.     $pdls = join(',', ($pdls, "PCL5"));
  1192.     } elsif ($dat->{'driver'} =~ /(laserjet|ljet|lj)/i) {
  1193.     $pdls = join(',', ($pdls, "PCL4"));
  1194.     }
  1195.     $pdls = join(',', ($dat->{'general_cmd'}, $pdls)) if 
  1196.     defined($dat->{'general_cmd'});
  1197.     if ($pdls) {
  1198.     for my $l (split(',', $pdls)) {
  1199.         my ($lang, $level) = ('', '');
  1200.         if ($l =~ /\b(PostScript|PS|BR-?Script)(\d?)\b/i) {
  1201.         $lang = "postscript";
  1202.         $level = $2;
  1203.         } elsif ($l =~ /\b(PDF)\b/i) {
  1204.         $lang = "pdf";
  1205.         } elsif ($l =~ /\b(PCLXL)\b/i) {
  1206.         $lang = "pcl";
  1207.         $level = "6";
  1208.         } elsif ($l =~ /\b(PCL)(\d\S?|)\b/i) {
  1209.         $lang = "pcl";
  1210.         $level = $2;
  1211.         if (!$level) {
  1212.             if ($dat->{'color'}) { 
  1213.             $level = "5c";
  1214.             } else {
  1215.             $level = "5e";
  1216.             }
  1217.         }
  1218.         } elsif ($l =~ /\b(PJL)\b/i) {
  1219.         $dat->{'pjl'} = 1;
  1220.         $dat->{'jcl'} = 1;
  1221.         }
  1222.         if ($lang) {
  1223.         if (!defined($dat->{'languages'})) {
  1224.             $dat->{'languages'} = [];
  1225.         }
  1226.         my $found = 0;
  1227.         foreach my $ll (@{$dat->{'languages'}}) {
  1228.             if ($ll->{'name'} =~ /^$lang$/i) {
  1229.             $ll->{'level'} = $level if $level && 
  1230.                                        ($level gt $ll->{'level'});
  1231.             $found = 1;
  1232.             }
  1233.         }
  1234.         push(@{$dat->{'languages'}},
  1235.              {
  1236.              'name' => $lang,
  1237.              'level' => $level
  1238.              }) if !$found;
  1239.         }
  1240.     }
  1241.     }
  1242.     $drivers{$dat->{'driver'}} = 1;
  1243.     for my $ll (@{$dat->{'languages'}}) {
  1244.     my $lang = $ll->{'name'};
  1245.     my $level = $ll->{'level'};
  1246.     if ($lang =~ /^postscript$/i) {
  1247.         if ($level eq "1") {
  1248.         $drivers{'Postscript1'} = 1;
  1249.         } else {
  1250.         $drivers{'Postscript'} = 1;
  1251.         }
  1252.     } elsif ($lang =~ /^pcl$/i) {
  1253.         if ($level eq "6") {
  1254.         if ($dat->{'color'}) {
  1255.             $drivers{'pxlcolor'} = 1;
  1256.         } else {
  1257.             $drivers{'pxlmono'} = 1;
  1258.             $drivers{'lj5gray'} = 1;
  1259.         }
  1260.         } elsif ($level eq "5e") {
  1261.         $drivers{'ljet4d'} = 1;
  1262.         $drivers{'ljet4'} = 1;
  1263.         $drivers{'lj4dith'} = 1;
  1264.         if ($dat->{'make'} =~ /^(HP|Hewlett[\s-]+Packard)$/i) {
  1265.             $drivers{'hplip'} = 1;
  1266.         } else {
  1267.             $drivers{'hpijs-pcl5e'} = 1;
  1268.         }
  1269.         $drivers{'gutenprint'} = 1;
  1270.         } elsif ($level eq "5c") {
  1271.         $drivers{'cljet5'} = 1;
  1272.         if ($dat->{'make'} =~ /^(HP|Hewlett[\s-]+Packard)$/i) {
  1273.             $drivers{'hplip'} = 1;
  1274.         } else {
  1275.             $drivers{'hpijs-pcl5c'} = 1;
  1276.         }
  1277.         } elsif ($level eq "5") {
  1278.         $drivers{'ljet3d'} = 1;
  1279.         $drivers{'ljet3'} = 1;
  1280.         } elsif ($level eq "4") {
  1281.         $drivers{'laserjet'} = 1;
  1282.         $drivers{'ljetplus'} = 1;
  1283.         $drivers{'ljet2p'} = 1;
  1284.         }
  1285.         # PCL printers print also plain text
  1286.         $dat->{'ascii'} = 'us-ascii';
  1287.     }
  1288.     }
  1289.     for my $drv (keys %drivers) {
  1290.     if (!defined($dat->{'drivers'})) {
  1291.         $dat->{'drivers'} = [];
  1292.     }
  1293.     my $found = 0;
  1294.     foreach my $dd (@{$dat->{'drivers'}}) {
  1295.         if (($dd->{'name'} =~ /^$drv$/i) ||
  1296.         ($dd->{'id'} =~ /^$drv$/i)) {
  1297.         $found = 1;
  1298.         }
  1299.         if ($ppddlpath && ($dd->{'id'} =~ /^$ppddrv$/i)) {
  1300.         $dd->{'ppd'} = $ppddlpath;
  1301.         }
  1302.     }
  1303.     push(@{$dat->{'drivers'}},
  1304.          {
  1305.          'name' => $drv,
  1306.          'id' => $drv,
  1307.          ($ppddlpath && ($drv =~ /^$ppddrv$/i) ?
  1308.           ('ppd' => $ppddlpath) : ())
  1309.          }) if !$found;
  1310.     }
  1311. }
  1312.  
  1313. sub ppdfromvartoperl {
  1314.  
  1315.     my ($ppd, $parameters) = @_;
  1316.  
  1317.     # Build a data structure for the renderer's command line and the
  1318.     # options
  1319.  
  1320.     my $dat = {};              # data structure for the options
  1321.     my $currentargument = "";  # We are currently reading this argument
  1322.     my $currentgroup = "";     # We are currently in this group/subgroup
  1323.     my @currentgrouptrans;     # Translation/long name for group/subgroup
  1324.     my $isfoomatic = 0;        # Do we have a Foomatic PPD?
  1325.  
  1326.     # If we have an old Foomatic 2.0.x PPD file, read its built-in Perl
  1327.     # data structure into @datablob and the default values in %ppddefaults
  1328.     # Then delete the $dat structure, replace it by the one "eval"ed from
  1329.     # @datablob, and correct the default settings according to the ones of
  1330.     # the main PPD structure
  1331.     my @datablob;
  1332.     
  1333.     $dat->{"encoding"} = "ascii";
  1334.  
  1335.     # search for LanguageEncoding
  1336.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1337.     $_ = $ppd->[$i];
  1338.     if (m/^\*LanguageEncoding:\s*(\S+)\s*$/) {
  1339.         # "*LanguageEncoding: <encoding>"        
  1340.         $dat->{'encoding'} = $1;
  1341.         if ($dat->{'encoding'} eq 'MacStandard') {
  1342.         $dat->{'encoding'} = 'MacCentralEurRoman'; 
  1343.         } elsif ($dat->{'encoding'} eq 'WindowsANSI') {
  1344.         $dat->{'encoding'} = 'cp1252'; 
  1345.         } elsif ($dat->{'encoding'} eq 'JIS83-RKSJ') {
  1346.         $dat->{'encoding'} = 'shiftjis';
  1347.         }
  1348.         last;
  1349.     }
  1350.     }
  1351.     # decode PPD
  1352.     my $encoding = $dat->{"encoding"};
  1353.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1354.     $ppd->[$i] = decode($encoding, $ppd->[$i]);
  1355.     }
  1356.  
  1357.     $dat->{'maxpaperwidth'} = 0;
  1358.  
  1359.     # Parse the PPD file
  1360.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1361.     $_ = $ppd->[$i];
  1362.     # Foomatic should also work with PPD files downloaded under
  1363.     # Windows.
  1364.     $_ = undossify($_);
  1365.     # Parse keywords
  1366.     if (m!^\*NickName:\s*\"(.*)$!) {
  1367.         # "*NickName: <code>"
  1368.         my $line = $1;
  1369.         # Store the value
  1370.         # Code string can have multiple lines, read all of them
  1371.         my $cmd = "";
  1372.         while ($line !~ m!\"!) {
  1373.         $line =~ s/^\s*//;
  1374.         $line =~ s/\s*$//;
  1375.         $cmd .= " $line";
  1376.         # Read next line
  1377.         $i ++;
  1378.         $line = $ppd->[$i];
  1379.         chomp $line;
  1380.         }
  1381.         $line =~ s/^\s*//;
  1382.         $line =~ m!^([^\"]*?)\s*\"!;
  1383.         $cmd .= " $1";
  1384.         $cmd =~ s/^\s*//;
  1385.         $dat->{'makemodel'} = unhexify($cmd);
  1386.         $dat->{'makemodel'} =~ s/^([^,]+),.*$/$1/;
  1387.     } elsif (m!^\*ModelName:\s*\"(.*)$!) {
  1388.         # "*ModelName: <code>"
  1389.         my $line = $1;
  1390.         # Store the value
  1391.         # Code string can have multiple lines, read all of them
  1392.         my $cmd = "";
  1393.         while ($line !~ m!\"!) {
  1394.         $line =~ s/^\s*//;
  1395.         $line =~ s/\s*$//;
  1396.         $cmd .= " $line";
  1397.         # Read next line
  1398.         $i ++;
  1399.         $line = $ppd->[$i];
  1400.         chomp $line;
  1401.         }
  1402.         $line =~ s/^\s*//;
  1403.         $line =~ m!^([^\"]*?)\s*\"!;
  1404.         $cmd .= " $1";
  1405.         $cmd =~ s/^\s*//;
  1406.         $dat->{'ppdmodelname'} = unhexify($cmd);
  1407.     } elsif (m!^\*Product:\s*\"(.*)$!) {
  1408.         # "*Product: <code>"
  1409.         my $line = $1;
  1410.         # Store the value
  1411.         # Code string can have multiple lines, read all of them
  1412.         my $cmd = "";
  1413.         while ($line !~ m!\"!) {
  1414.         $line =~ s/^\s*//;
  1415.         $line =~ s/\s*$//;
  1416.         $cmd .= " $line";
  1417.         # Read next line
  1418.         $i ++;
  1419.         $line = $ppd->[$i];
  1420.         chomp $line;
  1421.         }
  1422.         $line =~ s/^\s*//;
  1423.         $line =~ m!^([^\"]*?)\s*\"!;
  1424.         $cmd .= " $1";
  1425.         $cmd =~ s/^\s*//;
  1426.         my $ppdproduct = unhexify($cmd);
  1427.         $ppdproduct =~ s/^\s*\(\s*//;
  1428.         $ppdproduct =~ s/\s*\)\s*$//;
  1429.         @{$dat->{'ppdproduct'}} = ()
  1430.         if !defined($dat->{'ppdproduct'});
  1431.         push(@{$dat->{'ppdproduct'}}, $ppdproduct);
  1432.     } elsif (m!^\*Manufacturer:\s*\"(.*)$!) {
  1433.         # "*Manufacturer: <code>"
  1434.         my $line = $1;
  1435.         # Store the value
  1436.         # Code string can have multiple lines, read all of them
  1437.         my $cmd = "";
  1438.         while ($line !~ m!\"!) {
  1439.         $line =~ s/^\s*//;
  1440.         $line =~ s/\s*$//;
  1441.         $cmd .= " $line";
  1442.         # Read next line
  1443.         $i ++;
  1444.         $line = $ppd->[$i];
  1445.         chomp $line;
  1446.         }
  1447.         $line =~ s/^\s*//;
  1448.         $line =~ m!^([^\"]*?)\s*\"!;
  1449.         $cmd .= " $1";
  1450.         $cmd =~ s/^\s*//;
  1451.         $dat->{'ppdmanufacturer'} = unhexify($cmd);
  1452.     } elsif (m!^\*LanguageVersion:\s*(\S+)\s*$!) {
  1453.         # "*LanguageVersion: <language>"
  1454.         $dat->{'language'} = $1;
  1455.     } elsif (m!^\*ColorDevice:\s*(\S+)\s*$!) {
  1456.         # "*ColorDevice: <boolean>"
  1457.         my $col = $1;
  1458.         if ($col =~ /true/i) { 
  1459.         $dat->{'color'} = 1;
  1460.         } elsif ($col =~ /false/i) { 
  1461.         $dat->{'color'} = 0;
  1462.         }
  1463.     } elsif (m!^\*LanguageLevel:\s*\"?(\S+?)\"?\s*$!) {
  1464.         # "*LanguageLevel: "<level>""
  1465.         $dat->{'ppdpslevel'} = $1;
  1466.     } elsif (m!^\*Throughput:\s*\"?(\S+?)\"?\s*$!) {
  1467.         # "*Throughput: "<pages/min>""
  1468.         $dat->{'throughput'} = $1;
  1469.     } elsif (m!^\*1284DeviceID:\s*\"(.*)$!) {
  1470.         # "*1284DeviceID: <code>"
  1471.         my $line = $1;
  1472.         # Store the value
  1473.         # Code string can have multiple lines, read all of them
  1474.         my $cmd = "";
  1475.         while ($line !~ m!\"!) {
  1476.         $line =~ s/^\s*//;
  1477.         $line =~ s/\s*$//;
  1478.         $cmd .= $line;
  1479.         # Read next line
  1480.         $i ++;
  1481.         $line = $ppd->[$i];
  1482.         chomp $line;
  1483.         }
  1484.         $line =~ m!^([^\"]*?)\s*\"!;
  1485.         $cmd .= $1;
  1486.         $cmd =~ s/^\s*//;
  1487.         if (!defined($dat->{'general_ieee'}) ||
  1488.         (length($dat->{'general_ieee'}) <
  1489.          length($cmd))) {
  1490.         $dat->{'general_ieee'} = unhexify($cmd);
  1491.         if ($dat->{'general_ieee'} =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i) {
  1492.             $dat->{'general_mfg'} = $2;
  1493.         }
  1494.         if ($dat->{'general_ieee'} =~ /(MDL|MODEL):\s*([^:;]+);?/i) {
  1495.             $dat->{'general_mdl'} = $2;
  1496.         }
  1497.         if ($dat->{'general_ieee'} =~ /(CMD|COMMANDS?\s*SET):\s*([^:;]+);?/i) {
  1498.             $dat->{'general_cmd'} = $2;
  1499.         }
  1500.         if ($dat->{'general_ieee'} =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i) {
  1501.             $dat->{'general_des'} = $2;
  1502.         }
  1503.         }
  1504.     } elsif (m!^\*PaperDimension\s+([^:]+):\s*\"(.*)$!) {
  1505.         # "*PaperDimension <format>: <code>"
  1506.         my $line = $2;
  1507.         # Store the value
  1508.         # Code string can have multiple lines, read all of them
  1509.         my $cmd = "";
  1510.         while ($line !~ m!\"!) {
  1511.         $line =~ s/^\s*//;
  1512.         $line =~ s/\s*$//;
  1513.         $cmd .= " $line";
  1514.         # Read next line
  1515.         $i ++;
  1516.         $line = $ppd->[$i];
  1517.         chomp $line;
  1518.         }
  1519.         $line =~ s/^\s*//;
  1520.         $line =~ m!^([^\"]*?)\s*\"!;
  1521.         $cmd .= " $1";
  1522.         $cmd =~ s/^\s*//;
  1523.         $cmd =~ /^(\d+)/;
  1524.         my $width = $1;
  1525.         $dat->{'maxpaperwidth'} = $width if 
  1526.         $width && ($width > $dat->{'maxpaperwidth'});
  1527.     } elsif (m!^\*cupsFilter\s+([^:]+):\s*\"(.*)$!) {
  1528.         # "*cupsFilter: <code>"
  1529.         my $line = $2;
  1530.         # Store the value
  1531.         # Code string can have multiple lines, read all of them
  1532.         my $cmd = "";
  1533.         while ($line !~ m!\"!) {
  1534.         $line =~ s/^\s*//;
  1535.         $line =~ s/\s*$//;
  1536.         $cmd .= " $line";
  1537.         # Read next line
  1538.         $i ++;
  1539.         $line = $ppd->[$i];
  1540.         chomp $line;
  1541.         }
  1542.         $line =~ s/^\s*//;
  1543.         $line =~ m!^([^\"]*?)\s*\"!;
  1544.         $cmd .= " $1";
  1545.         $cmd =~ s/^\s*//;
  1546.         push(@{$dat->{'cupsfilterlines'}}, $cmd);
  1547.     } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) {
  1548.         # "*FoomaticIDs: <printer ID> <driver ID>"
  1549.         my $id = $1;
  1550.         my $driver = $2;
  1551.         # Store the values
  1552.         $dat->{'id'} = $id;
  1553.         $dat->{'driver'} = $driver;
  1554.         $isfoomatic = 1;
  1555.     } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1556.         # "*FoomaticRIPPostPipe: <code>"
  1557.         my $line = $1;
  1558.         # Store the value
  1559.         # Code string can have multiple lines, read all of them
  1560.         my $cmd = "";
  1561.         while ($line !~ m!\"!) {
  1562.         if ($line =~ m!&&$!) {
  1563.             # line continues in next line
  1564.             $cmd .= substr($line, 0, -2);
  1565.         } else {
  1566.             # line ends here
  1567.             $cmd .= "$line\n";
  1568.         }
  1569.         # Read next line
  1570.         $i ++;
  1571.         $line = $ppd->[$i];
  1572.         chomp $line;
  1573.         }
  1574.         $line =~ m!^([^\"]*)\"!;
  1575.         $cmd .= $1;
  1576.         $dat->{'postpipe'} = unhtmlify($cmd);
  1577.     } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) {
  1578.         # "*FoomaticRIPCommandLine: <code>"
  1579.         my $line = $1;
  1580.         # Store the value
  1581.         # Code string can have multiple lines, read all of them
  1582.         my $cmd = "";
  1583.         while ($line !~ m!\"!) {
  1584.         if ($line =~ m!&&$!) {
  1585.             # line continues in next line
  1586.             $cmd .= substr($line, 0, -2);
  1587.         } else {
  1588.             # line ends here
  1589.             $cmd .= "$line\n";
  1590.         }
  1591.         # Read next line
  1592.         $i ++;
  1593.         $line = $ppd->[$i];
  1594.         chomp $line;
  1595.         }
  1596.         $line =~ m!^([^\"]*)\"!;
  1597.         $cmd .= $1;
  1598.         $dat->{'cmd'} = unhtmlify($cmd);
  1599.     } elsif (m!^\*FoomaticRIPCommandLinePDF:\s*\"(.*)$!) {
  1600.         # "*FoomaticRIPCommandLinePDF: <code>"
  1601.         my $line = $1;
  1602.         # Store the value
  1603.         # Code string can have multiple lines, read all of them
  1604.         my $cmd = "";
  1605.         while ($line !~ m!\"!) {
  1606.         if ($line =~ m!&&$!) {
  1607.             # line continues in next line
  1608.             $cmd .= substr($line, 0, -2);
  1609.         } else {
  1610.             # line ends here
  1611.             $cmd .= "$line\n";
  1612.         }
  1613.         # Read next line
  1614.         $i ++;
  1615.         $line = $ppd->[$i];
  1616.         chomp $line;
  1617.         }
  1618.         $line =~ m!^([^\"]*)\"!;
  1619.         $cmd .= $1;
  1620.         $dat->{'cmd_pdf'} = unhtmlify($cmd);
  1621.     } elsif (m!^\*FoomaticRIPNoPageAccounting:\s*(\S+)\s*$!) {
  1622.         # "*FoomaticRIPNoPageAccounting: <boolean value>"
  1623.         my $value = $1;
  1624.         # Store the value
  1625.         if ($value =~ /^True$/i) {
  1626.         $dat->{'drivernopageaccounting'} = 1;
  1627.         } else {
  1628.         delete $dat->{'drivernopageaccounting'};
  1629.         }
  1630.     } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) {
  1631.         # "*CustomPageSize True: <code>"
  1632.         my $setting = "Custom";
  1633.         my $translation = "Custom Size";
  1634.         my $line = $1;
  1635.         # Make sure that the argument is in the data structure
  1636.         checkarg ($dat, "PageSize");
  1637.         checkarg ($dat, "PageRegion");
  1638.         # "PageSize" and "PageRegion" must be both user-visible as they are
  1639.         # options required by the PPD spec
  1640.         undef $dat->{'args_byname'}{"PageSize"}{'hidden'};
  1641.         undef $dat->{'args_byname'}{"PageRegion"}{'hidden'};
  1642.         # Make sure that the setting is in the data structure
  1643.         checksetting ($dat, "PageSize", $setting);
  1644.         checksetting ($dat, "PageRegion", $setting);
  1645.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1646.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1647.         # Store the value
  1648.         # Code string can have multiple lines, read all of them
  1649.         my $code = "";
  1650.         while ($line !~ m!\"!) {
  1651.         if ($line =~ m!&&$!) {
  1652.             # line continues in next line
  1653.             $code .= substr($line, 0, -2);
  1654.         } else {
  1655.             # line ends here
  1656.             $code .= "$line\n";
  1657.         }
  1658.         # Read next line
  1659.         $i ++;
  1660.         $line = $ppd->[$i];
  1661.         chomp $line;
  1662.         }
  1663.         $line =~ m!^([^\"]*)\"!;
  1664.         $code .= $1;
  1665.         if ($code !~ m!^%% FoomaticRIPOptionSetting!m) {
  1666.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1667.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1668.         }
  1669.     } elsif (m!^\*Open(Sub|)Group:\s*\*?([^/]+?)(/(.*)|)$!) {
  1670.         # "*Open[Sub]Group: <group>[/<translation>]
  1671.         my $group = $2;
  1672.         chomp($group) if $group;
  1673.         my $grouptrans = $4;
  1674.         chomp($grouptrans) if $grouptrans;
  1675.         if (!$grouptrans) {
  1676.         $grouptrans = longname($group);
  1677.         }
  1678.         if ($currentgroup) {
  1679.         $currentgroup .= "/";
  1680.         }
  1681.         $currentgroup .= $group;
  1682.         push(@currentgrouptrans, 
  1683.          unhexify($grouptrans, $dat->{"encoding"}));
  1684.     } elsif (m!^\*Close(Sub|)Group:?\s*\*?([^/]+?)$!) {
  1685.         # "*Close[Sub]Group: <group>"
  1686.         my $group = $2;
  1687.         chomp($group) if $group;
  1688.         $currentgroup =~ s!$group$!!;
  1689.         $currentgroup =~ s!/$!!;
  1690.         pop(@currentgrouptrans);
  1691.     } elsif (m!^\*Close(Sub|)Group\s*$!) {
  1692.         # "*Close[Sub]Group"
  1693.         # NOTE: This expression is not Adobe-conforming
  1694.         $currentgroup =~ s![^/]+$!!;
  1695.         $currentgroup =~ s!/$!!;
  1696.         pop(@currentgrouptrans);
  1697.     } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) {
  1698.         # "*[JCL]OpenUI *<option>[/<translation>]: <type>"
  1699.         my $argnametrans = $2;
  1700.         my $argtype = $3;
  1701.         my $argname;
  1702.         my $translation = "";
  1703.         if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1704.         $argname = $1;
  1705.         $translation = $2;
  1706.         } else {
  1707.         $argname = $argnametrans;
  1708.         }
  1709.         # Make sure that the argument is in the data structure
  1710.         checkarg ($dat, $argname);
  1711.         # This option has a non-Foomatic keyword, so this is not
  1712.         # a hidden option
  1713.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  1714.         # Store the values
  1715.         $dat->{'args_byname'}{$argname}{'comment'} = 
  1716.         unhexify($translation, $dat->{"encoding"});
  1717.         $dat->{'args_byname'}{$argname}{'group'} = $currentgroup;
  1718.         @{$dat->{'args_byname'}{$argname}{'grouptrans'}} =
  1719.         @currentgrouptrans;
  1720.         # Set the argument type only if not defined yet, a
  1721.         # definition in "*FoomaticRIPOption" has priority
  1722.         if (!defined($dat->{'args_byname'}{$argname}{'type'})) {
  1723.         if ($argtype eq "PickOne") {
  1724.             $dat->{'args_byname'}{$argname}{'type'} = 'enum';
  1725.         } elsif ($argtype eq "PickMany") {
  1726.             $dat->{'args_byname'}{$argname}{'type'} = 'pickmany';
  1727.         } elsif ($argtype eq "Boolean") {
  1728.             $dat->{'args_byname'}{$argname}{'type'} = 'bool';
  1729.         }
  1730.         }
  1731.         # Mark in which argument we are currently, so that we can find
  1732.         # the entries for the choices
  1733.         $currentargument = $argname;
  1734.     } elsif (m!^\*(JCL|)CloseUI:?\s+\*([^:/\s]+)\s*$!) {
  1735.         next if !$currentargument;
  1736.         # "*[JCL]CloseUI: *<option>"
  1737.         my $argname = $2;
  1738.         # Unmark the current argument to do not mis-interpret any 
  1739.         # keywords as choices
  1740.         $currentargument = "";
  1741.     } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s*$!) ||
  1742.          (m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s+(\S+)\s*$!)){
  1743.         # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]"
  1744.         # <order> only used for 1-choice enum options
  1745.         my $argname = $1;
  1746.         my $argtype = $2;
  1747.         my $argstyle = $3;
  1748.         my $spot = $4;
  1749.         my $order = $5;
  1750.         # Make sure that the argument is in the data structure
  1751.         checkarg ($dat, $argname);
  1752.         # Store the values
  1753.         $dat->{'args_byname'}{$argname}{'type'} = $argtype;
  1754.         if ($argstyle eq "PS") {
  1755.         $dat->{'args_byname'}{$argname}{'style'} = 'G';
  1756.         } elsif ($argstyle eq "CmdLine") {
  1757.         $dat->{'args_byname'}{$argname}{'style'} = 'C';
  1758.         } elsif ($argstyle eq "JCL") {
  1759.         $dat->{'args_byname'}{$argname}{'style'} = 'J';
  1760.         $dat->{'jcl'} = 1;
  1761.         $dat->{'pjl'} = 1;
  1762.         } elsif ($argstyle eq "Composite") {
  1763.         $dat->{'args_byname'}{$argname}{'style'} = 'X';
  1764.         }
  1765.         $dat->{'args_byname'}{$argname}{'spot'} = $spot;
  1766.         # $order only defined here for 1-choice enum options
  1767.         if ($order) {
  1768.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  1769.         }
  1770.     } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) {
  1771.         # "*FoomaticRIPOptionPrototype <option>: <code>"
  1772.         # Used for numerical and string options only
  1773.         my $argname = $1;
  1774.         my $line = $2;
  1775.         # Make sure that the argument is in the data structure
  1776.         checkarg ($dat, $argname);
  1777.         # Store the value
  1778.         # Code string can have multiple lines, read all of them
  1779.         my $proto = "";
  1780.         while ($line !~ m!\"!) {
  1781.         if ($line =~ m!&&$!) {
  1782.             # line continues in next line
  1783.             $proto .= substr($line, 0, -2);
  1784.         } else {
  1785.             # line ends here
  1786.             $proto .= "$line\n";
  1787.         }
  1788.         # Read next line
  1789.         $i ++;
  1790.         $line = $ppd->[$i];
  1791.         chomp $line;
  1792.         }
  1793.         $line =~ m!^([^\"]*)\"!;
  1794.         $proto .= $1;
  1795.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto);
  1796.     } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*(\S+)\s+(\S+)\s*$!) {
  1797.         # "*FoomaticRIPOptionRange <option>: <min> <max>"
  1798.         # Used for numerical options only
  1799.         my $argname = $1;
  1800.         my $min = $2;
  1801.         my $max = $3;
  1802.         # Make sure that the argument is in the data structure
  1803.         checkarg ($dat, $argname);
  1804.         # Store the values
  1805.         $dat->{'args_byname'}{$argname}{'min'} = $min;
  1806.         $dat->{'args_byname'}{$argname}{'max'} = $max;
  1807.     } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*(\S+)\s*$!) {
  1808.         # "*FoomaticRIPOptionMaxLength <option>: <length>"
  1809.         # Used for string options only
  1810.         my $argname = $1;
  1811.         my $maxlength = $2;
  1812.         # Make sure that the argument is in the data structure
  1813.         checkarg ($dat, $argname);
  1814.         # Store the value
  1815.         $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength;
  1816.     } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) {
  1817.         # "*FoomaticRIPOptionAllowedChars <option>: <code>"
  1818.         # Used for string options only
  1819.         my $argname = $1;
  1820.         my $line = $2;
  1821.         # Store the value
  1822.         # Code string can have multiple lines, read all of them
  1823.         my $code = "";
  1824.         while ($line !~ m!\"!) {
  1825.         if ($line =~ m!&&$!) {
  1826.             # line continues in next line
  1827.             $code .= substr($line, 0, -2);
  1828.         } else {
  1829.             # line ends here
  1830.             $code .= "$line\n";
  1831.         }
  1832.         # Read next line
  1833.         $i ++;
  1834.         $line = $ppd->[$i];
  1835.         chomp $line;
  1836.         }
  1837.         $line =~ m!^([^\"]*)\"!;
  1838.         $code .= $1;
  1839.         # Make sure that the argument is in the data structure
  1840.         checkarg ($dat, $argname);
  1841.         # Store the value
  1842.         $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code);
  1843.     } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) {
  1844.         # "*FoomaticRIPOptionAllowedRegExp <option>: <code>"
  1845.         # Used for string options only
  1846.         my $argname = $1;
  1847.         my $line = $2;
  1848.         # Store the value
  1849.         # Code string can have multiple lines, read all of them
  1850.         my $code = "";
  1851.         while ($line !~ m!\"!) {
  1852.         if ($line =~ m!&&$!) {
  1853.             # line continues in next line
  1854.             $code .= substr($line, 0, -2);
  1855.         } else {
  1856.             # line ends here
  1857.             $code .= "$line\n";
  1858.         }
  1859.         # Read next line
  1860.         $i ++;
  1861.         $line = $ppd->[$i];
  1862.         chomp $line;
  1863.         }
  1864.         $line =~ m!^([^\"]*)\"!;
  1865.         $code .= $1;
  1866.         # Make sure that the argument is in the data structure
  1867.         checkarg ($dat, $argname);
  1868.         # Store the value
  1869.         $dat->{'args_byname'}{$argname}{'allowedregexp'} =
  1870.         unhtmlify($code);
  1871.     } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) {
  1872.         next if !$currentargument;
  1873.         # "*OrderDependency: <order> <section> *<option>"
  1874.         my $order = $1;
  1875.         my $section = $2;
  1876.         my $argname = $3;
  1877.         # Make sure that the argument is in the data structure
  1878.         checkarg ($dat, $argname);
  1879.         # This option has a non-Foomatic keyword, so this is not
  1880.         # a hidden option
  1881.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  1882.         # Store the values
  1883.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  1884.         $dat->{'args_byname'}{$argname}{'section'} = $section;
  1885.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1886.         # "*Default<option>: <value>"
  1887.         my $argname = $1;
  1888.         my $default = $2;
  1889.         # Make sure that the argument is in the data structure
  1890.         checkarg ($dat, $argname);
  1891.         # Store the value
  1892.         $dat->{'args_byname'}{$argname}{'default'} = $default;
  1893.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1894.         # "*FoomaticRIPDefault<option>: <value>"
  1895.         # Used for numerical options only
  1896.         my $argname = $1;
  1897.         my $default = $2;
  1898.         # Make sure that the argument is in the data structure
  1899.         checkarg ($dat, $argname);
  1900.         # Store the value
  1901.         $dat->{'args_byname'}{$argname}{'fdefault'} = $default;
  1902.     } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) {
  1903.         next if !$currentargument;
  1904.         # "*<option> <choice>[/<translation>]: <code>"
  1905.         my $settingtrans = $1;
  1906.         my $line = $2;
  1907.         my $translation = "";
  1908.         my $setting = "";
  1909.         if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1910.         $setting = $1;
  1911.         $translation = $2;
  1912.         } else {
  1913.         $setting = $settingtrans;
  1914.         }
  1915.         $translation = unhexify($translation, $dat->{"encoding"});
  1916.         # Make sure that the argument is in the data structure
  1917.         checkarg ($dat, $currentargument);
  1918.         # This option has a non-Foomatic keyword, so this is not
  1919.         # a hidden option
  1920.         undef $dat->{'args_byname'}{$currentargument}{'hidden'};
  1921.         # Make sure that the setting is in the data structure (enum
  1922.         # options)
  1923.         my $bool =
  1924.         ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool');
  1925.         if ($bool) {
  1926.         if (lc($setting) eq "true") {
  1927.             if (!$dat->{'args_byname'}{$currentargument}{'comment'}) {
  1928.             $dat->{'args_byname'}{$currentargument}{'comment'} =
  1929.                 $translation;
  1930.             }
  1931.             $dat->{'args_byname'}{$currentargument}{'comment_true'} =
  1932.             $translation;
  1933.         } else {
  1934.             $dat->{'args_byname'}{$currentargument}{'comment_false'} =
  1935.             $translation;
  1936.         }
  1937.         } else {
  1938.         checksetting ($dat, $currentargument, $setting);
  1939.         $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation;
  1940.         # Make sure that this argument has a default setting, even
  1941.         # if none is defined in this PPD file
  1942.         if (!defined($dat->{'args_byname'}{$currentargument}{'default'}) ||
  1943.             ($dat->{'args_byname'}{$currentargument}{'default'} eq "")) {
  1944.             $dat->{'args_byname'}{$currentargument}{'default'} = $setting;
  1945.         }
  1946.         }
  1947.         # Store the value
  1948.         # Code string can have multiple lines, read all of them
  1949.         my $code = "";
  1950.         while ($line !~ m!\"!) {
  1951.         if ($line =~ m!&&$!) {
  1952.             # line continues in next line
  1953.             $code .= substr($line, 0, -2);
  1954.         } else {
  1955.             # line ends here
  1956.             $code .= "$line\n";
  1957.         }
  1958.         # Read next line
  1959.         $i ++;
  1960.         $line = $ppd->[$i];
  1961.         chomp $line;
  1962.         }
  1963.         $line =~ m!^([^\"]*)\"!;
  1964.         $code .= $1;
  1965.         if ($code !~ m!^%% FoomaticRIPOptionSetting!) {
  1966.         if ($bool) {
  1967.             if (lc($setting) eq "true") {
  1968.             $dat->{'args_byname'}{$currentargument}{'proto'} =
  1969.                 $code;
  1970.             } else {
  1971.             $dat->{'args_byname'}{$currentargument}{'protof'} =
  1972.                 $code;
  1973.             }
  1974.         } else {
  1975.             $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code;
  1976.         }
  1977.         }
  1978.     } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) ||
  1979.          (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) {
  1980.         # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>"
  1981.         # For boolean options <choice> is not given
  1982.         my $argname = $1;
  1983.         my $setting = $2;
  1984.         my $line = $3;
  1985.         my $bool = 0;
  1986.         if (!$line) {
  1987.         $line = $setting;
  1988.         $bool = 1;
  1989.         }
  1990.         # Make sure that the argument is in the data structure
  1991.         checkarg ($dat, $argname);
  1992.         # Make sure that the setting is in the data structure (enum
  1993.         # options)
  1994.         if (!$bool) {
  1995.         checksetting ($dat, $argname, $setting);
  1996.         # Make sure that this argument has a default setting, even
  1997.         # if none is defined in this PPD file
  1998.         if (!$dat->{'args_byname'}{$argname}{'default'}) {
  1999.             $dat->{'args_byname'}{$argname}{'default'} = $setting;
  2000.         }
  2001.         }
  2002.         # Store the value
  2003.         # Code string can have multiple lines, read all of them
  2004.         my $code = "";
  2005.         while ($line !~ m!\"!) {
  2006.         if ($line =~ m!&&$!) {
  2007.             # line continues in next line
  2008.             $code .= substr($line, 0, -2);
  2009.         } else {
  2010.             # line ends here
  2011.             $code .= "$line\n";
  2012.         }
  2013.         # Read next line
  2014.         $i ++;
  2015.         $line = $ppd->[$i];
  2016.         chomp $line;
  2017.         }
  2018.         $line =~ m!^([^\"]*)\"!;
  2019.         $code .= $1;
  2020.         if ($bool) {
  2021.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code);
  2022.         } else {
  2023.         $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code);
  2024.         }
  2025.     } elsif (m!^\*JCL(Begin|ToPSInterpreter|End):\s*\"(.*)$!) {
  2026.         # "*JCL(Begin|ToPSInterpreter|End): <code>"
  2027.         # The printer supports PJL/JCL when there is such a line 
  2028.         $dat->{'jcl'} = 1;
  2029.         $dat->{'pjl'} = 1;
  2030.         my $item = $1;
  2031.         my $line = $2;
  2032.         # Store the value
  2033.         # Code string can have multiple lines, read all of them
  2034.         my $code = "";
  2035.         while ($line !~ m!\"!) {
  2036.         if ($line =~ m!&&$!) {
  2037.             # line continues in next line
  2038.             $code .= substr($line, 0, -2);
  2039.         } else {
  2040.             # line ends here
  2041.             $code .= "$line\n";
  2042.         }
  2043.         # Read next line
  2044.         $i ++;
  2045.         $line = $ppd->[$i];
  2046.         chomp $line;
  2047.         }
  2048.         $line =~ m!^([^\"]*)\"!;
  2049.         $code .= $1;
  2050.         $code = unhexify($code, $dat->{"encoding"});
  2051.         if ($item eq 'Begin') {
  2052.         $dat->{'jclbegin'} = $code;
  2053.         } elsif ($item eq 'ToPSInterpreter') {
  2054.         $dat->{'jcltointerpreter'} = $code;
  2055.         } elsif ($item eq 'End') {
  2056.         $dat->{'jclend'} = $code;
  2057.         }
  2058.     } elsif (m!^\*\% COMDATA \#(.*)$!) {
  2059.         # If we have an old Foomatic 2.0.x PPD file, collect its Perl 
  2060.         # data
  2061.         push (@datablob, $1);
  2062.     #} elsif (m!(laser|toner)!i) {
  2063.     #    $dat->{'type'} = "laser";
  2064.     #} elsif (m!(ink|nozzle)!i) {
  2065.     #    $dat->{'type'} ||= "inkjet";
  2066.     }
  2067.     }
  2068.  
  2069.     # If we have an old Foomatic 2.0.x PPD file use its Perl data structure
  2070.     if ($#datablob >= 0) {
  2071.     my $VAR1;
  2072.     if (eval join('',@datablob)) {
  2073.         # Overtake default settings from the main structure of the
  2074.         # PPD file
  2075.         for my $arg (@{$dat->{'args'}}) {
  2076.         if ($arg->{'default'}) {
  2077.             $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} = 
  2078.             $arg->{'default'};
  2079.         }
  2080.         }
  2081.         undef $dat;
  2082.         $dat = $VAR1;
  2083.         $dat->{'jcl'} = $dat->{'pjl'};
  2084.         $isfoomatic = 1;
  2085.     } else {
  2086.         # Perl structure broken
  2087.         warn "\nUnable to evaluate datablob, print jobs may come " .
  2088.         "out incorrectly or not at all.\n\n";
  2089.     }
  2090.     }
  2091.  
  2092.     # Set manufacturer and model fields
  2093.     if (defined($dat->{'ppdmanufacturer'})) {
  2094.     $dat->{'make'} = $dat->{'ppdmanufacturer'};
  2095.     } elsif (defined($dat->{'general_mfg'})) {
  2096.     $dat->{'make'} = $dat->{'general_mfg'};
  2097.     } elsif (defined($dat->{'makemodel'})) {
  2098.     ($dat->{'make'}, $dat->{'model'}) = guessmake($dat->{'makemodel'});
  2099.     $dat->{'model'} =~ s/^(.*?)\s*(,|Foomatic|CUPS|\(?\d+\.\d+\)?)/$1/i;
  2100.     }
  2101.     if (defined($dat->{'ppdmodelname'})) {
  2102.     (my $dummy, $dat->{'model'}) = guessmake($dat->{'ppdmodelname'});
  2103.     } elsif (defined($dat->{'ppdproduct'}) &&
  2104.          (scaler(@{$dat->{'ppdproduct'}}) == 1)) {
  2105.     $dat->{'model'} = $dat->{'ppdproduct'}[0];
  2106.     } elsif (!$dat->{'model'} && defined($dat->{'general_mdl'})) {
  2107.     $dat->{'model'} = $dat->{'general_mdl'};
  2108.     } elsif (defined($dat->{'ppdproduct'})) {
  2109.     $dat->{'model'} = $dat->{'ppdproduct'}[0];
  2110.     }
  2111.     $dat->{'make'} = clean_manufacturer_name($dat->{'make'});
  2112.     $dat->{'model'} = clean_manufacturer_name($dat->{'model'});
  2113.     ($dat->{'make'}, $dat->{'model'}) = guessmake($dat->{'model'})
  2114.     if !$dat->{'make'};
  2115.     $dat->{'model'} =~ s/^\s*$dat->{'make'}\s+//i;
  2116.     $dat->{'model'} = clean_model_name($dat->{'model'});
  2117.  
  2118.     # Generate a device ID if none was supplied. The PPD specs
  2119.     # expect the make and model of the device ID in the *Manufacturer
  2120.     # and *Product fields of the PPD.
  2121.     $dat->{'general_mfg'} = $dat->{'ppdmanufacturer'} if 
  2122.     $dat->{'ppdmanufacturer'} && !$dat->{'general_mfg'};
  2123.     $dat->{'general_mdl'} = $dat->{'ppdproduct'}[0] if 
  2124.     $dat->{'ppdproduct'} && !$dat->{'general_mdl'};
  2125.     $dat->{'general_ieee'} = "MFG:" . $dat->{'general_mfg'} .
  2126.     ";MDL:" . $dat->{'general_mdl'} . ";" if 
  2127.     $dat->{'general_mfg'} && $dat->{'general_mdl'} &&
  2128.     !$dat->{'general_ieee'};
  2129.  
  2130.     # Generate the Foomatic printer ID
  2131.     $dat->{'id'} = generatepid($dat->{'make'}, $dat->{'model'})
  2132.     if !$dat->{'id'};
  2133.  
  2134.     # Find out printer's page description languages and suitable drivers
  2135.     apply_driver_and_pdl_info($dat, $parameters);
  2136.  
  2137.     # Find the maximum resolution
  2138.     if (defined($dat->{'args_byname'}{'Resolution'})) {
  2139.     my $maxres = 0;
  2140.     my $maxxres = 0;
  2141.     my $maxyres = 0;
  2142.     for my $reschoice (keys(%{$dat->{'args_byname'}{'Resolution'}{'vals_byname'}})) {
  2143.         my $r;
  2144.         my $x;
  2145.         my $y;
  2146.         if ($reschoice =~ /^(\d+)x(\d+)dpi$/i) {
  2147.         $x = $1;
  2148.         $y = $2;
  2149.         } elsif ($reschoice =~ /^(\d+)dpi$/i) {
  2150.         $x = $1;
  2151.         $y = $x;
  2152.         }
  2153.         $r = $x * $y;
  2154.         if ($r >= $maxres) {
  2155.         $maxres = $r;
  2156.         $maxxres = $x;
  2157.         $maxyres = $y
  2158.         }
  2159.     }
  2160.     if ($maxres == 0) {
  2161.         if (defined($dat->{'args_byname'}{'Resolution'}{'default'})) {
  2162.         my $res = $dat->{'args_byname'}{'Resolution'}{'default'};
  2163.         if ($res =~ /^(\d+)x(\d+)dpi$/i) {
  2164.             $dat->{'maxxres'} = $1;
  2165.             $dat->{'maxyres'} = $2;
  2166.         } elsif ($res =~ /^(\d+)dpi$/i) {
  2167.             $dat->{'maxxres'} = $1;
  2168.             $dat->{'maxyres'} = $dat->{'maxxres'};
  2169.         }
  2170.         }
  2171.     } else {
  2172.         $dat->{'maxxres'} = $maxxres;
  2173.         $dat->{'maxyres'} = $maxyres;
  2174.     }
  2175.     }
  2176.  
  2177.     if ($dat->{'maxpaperwidth'}) {
  2178.     my $wi = sprintf("%.1f", $dat->{'maxpaperwidth'} / 72);
  2179.     my $wc = sprintf("%.1f", $dat->{'maxpaperwidth'} / 72 * 2.54);
  2180.     my $wcomm = ($dat->{'maxpaperwidth'} < 280 ?
  2181.              "Label/Card printer" :
  2182.              ($dat->{'maxpaperwidth'} < 600 ?
  2183.               "Photo printer" :
  2184.               ($dat->{'maxpaperwidth'} < 800 ?
  2185.                "Standard format printer" :
  2186.                ($dat->{'maxpaperwidth'} < 1500 ?
  2187.             "Wide format printer" :
  2188.             "Large format printer"))));
  2189.     $dat->{'comment'} .=
  2190.         "      Maximum paper width: " . $wi . " inches / " . $wc .
  2191.         " cm\n" .
  2192.         "      (" . $wcomm . ")<p>\n\n" if $dat->{'maxpaperwidth'};
  2193.     }
  2194.     $dat->{'comment'} .=
  2195.     "      Printing engine speed: " . $dat->{'throughput'} .
  2196.     " pages/min<p>\n\n" if
  2197.     defined($dat->{'throughput'}) && ($dat->{'throughput'} > 1);
  2198.  
  2199.     # Set the defaults for the numerical options, taking into account
  2200.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  2201.     numericaldefaults($dat);
  2202.  
  2203.     # Some clean-up
  2204.     checklongnames($dat);
  2205.     generalentries($dat);
  2206.  
  2207.     return $dat;
  2208. }
  2209.  
  2210. sub generatepid {
  2211.     # Generate the Foomatic printer ID
  2212.     my ($mk, $md) = @_;
  2213.     $mk =~ s/\s+/_/g;
  2214.     $mk =~ s/\+/plus/g;
  2215.     $mk =~ s/[^A-Za-z0-9\._]/_/g;
  2216.     $mk =~ s/_+/_/g;
  2217.     $mk =~ s/^_//;
  2218.     $mk =~ s/_$//;
  2219.     $md =~ s/\s+/_/g;
  2220.     $md =~ s/\+/plus/g;
  2221.     $md =~ s/[^A-Za-z0-9\.\-]/_/g;
  2222.     $md =~ s/_+/_/g;
  2223.     $md =~ s/^_//;
  2224.     $md =~ s/_$//;
  2225.     return "$mk-$md";
  2226. }
  2227.  
  2228. sub perltoxml {
  2229.     my ($this, $mode) = @_;
  2230.  
  2231.     my $dat = $this->{'dat'};
  2232.     my $xml = "";
  2233.  
  2234.     $xml .= "<foomatic>\n" if !$mode || ($mode =~ /^c/i); 
  2235.  
  2236.     if (!$mode || ($mode =~ /^[cp]/i)) { 
  2237.     $xml .=
  2238.         "<printer id=\"printer/" . $dat->{'id'} . "\">\n" .
  2239.         "  <make>" . $dat->{'make'} . "</make>\n" .
  2240.         "  <model>" . $dat->{'model'} . "</model>\n" .
  2241.         "  <mechanism>\n" .
  2242.         ($dat->{'type'} ? "    <" . $dat->{'type'} . "/>\n" : ()) .
  2243.         ($dat->{'color'} ? "    <color/>\n" : ()) .
  2244.         ($dat->{'maxxres'} || $dat->{'maxyres'} ?
  2245.          "    <resolution>\n" .
  2246.          "      <dpi>\n" .
  2247.          ($dat->{'maxxres'} ?
  2248.           "        <x>" . $dat->{'maxxres'} . "</x>\n" : ()) .
  2249.          ($dat->{'maxyres'} ?
  2250.           "        <y>" . $dat->{'maxyres'} . "</y>\n" : ()) .
  2251.          "      </dpi>\n" .
  2252.          "    </resolution>\n" : ()) .
  2253.          "  </mechanism>\n";
  2254.     if (defined($dat->{'languages'}) ||
  2255.         defined($dat->{'pjl'}) ||
  2256.         defined($dat->{'ascii'})) {
  2257.         $xml .= "  <lang>\n";
  2258.         if (defined($dat->{'languages'})) {
  2259.         for  my $lang (@{$dat->{'languages'}}) {
  2260.             $xml .= "    <" . $lang->{'name'};
  2261.             if ($lang->{'level'}) {
  2262.             $xml .= " level=\"" . $lang->{'level'} . "\" ";
  2263.             }
  2264.             $xml .= "/>\n";
  2265.         }
  2266.         }
  2267.         if (defined($dat->{'pjl'})) {
  2268.         $xml .= "    <pjl />\n";
  2269.         }
  2270.         if (defined($dat->{'ascii'})) {
  2271.         $xml .= "    <text>\n";
  2272.         $xml .= "      <charset>us-ascii</charset>\n";
  2273.         $xml .= "    </text>\n";
  2274.         }
  2275.         $xml .= "  </lang>\n";
  2276.     }
  2277.     if (defined($dat->{'general_ieee'}) ||
  2278.         defined($dat->{'general_mfg'}) ||
  2279.         defined($dat->{'general_mdl'}) ||
  2280.         defined($dat->{'general_des'}) ||
  2281.         defined($dat->{'general_cmd'})) {
  2282.         $xml .= "  <autodetect>\n";
  2283.         $xml .= "    <general>\n";
  2284.         $xml .= "      <ieee1284>" . $dat->{'general_ieee'} .
  2285.         "</ieee1284>\n" if defined($dat->{'general_ieee'});
  2286.         $xml .= "      <manufacturer>" . $dat->{'general_mfg'} .
  2287.         "</manufacturer>\n" if defined($dat->{'general_mfg'});
  2288.         $xml .= "      <model>" . $dat->{'general_mdl'} .
  2289.         "</model>\n" if defined($dat->{'general_mdl'});
  2290.         $xml .= "      <description>" . $dat->{'general_des'} .
  2291.         "</description>\n" if defined($dat->{'general_des'});
  2292.         $xml .= "      <commandset>" . $dat->{'general_cmd'} .
  2293.         "</commandset>\n" if defined($dat->{'general_cmd'});
  2294.         $xml .= "    </general>\n";
  2295.         $xml .= "  </autodetect>\n";
  2296.     }
  2297.     $xml .= "  <functionality>" . $dat->{'functionality'} .
  2298.         "</functionality>\n" if defined($dat->{'functionality'});
  2299.     $xml .= "  <driver>" . $dat->{'driver'} .
  2300.         "</driver>\n" if defined($dat->{'driver'});
  2301.     if (defined($dat->{'drivers'})) {
  2302.         $xml .= "  <drivers>\n";
  2303.         for  my $drv (@{$dat->{'drivers'}}) {
  2304.         $xml .= "    <driver>\n";
  2305.         $xml .= "      <id>" . $drv->{'id'} . "</id>\n"
  2306.             if defined($drv->{'id'});
  2307.         $xml .= "      <ppd>" . $drv->{'ppd'} . "</ppd>\n"
  2308.             if defined($drv->{'ppd'});
  2309.         $xml .= "    </driver>\n";
  2310.         }
  2311.         $xml .= "  </drivers>\n";
  2312.     }
  2313.     $xml .= "  <unverified />\n" if $dat->{'unverified'};
  2314.     $xml .=
  2315.         "  <comments>\n" .
  2316.         "    <en>\n";
  2317.     $xml .= htmlify($dat->{'comment'}) . "\n" if $dat->{'comment'};
  2318.     $xml .=
  2319.         "    </en>\n" .
  2320.         "  </comments>\n" .
  2321.         "</printer>\n";
  2322.     }
  2323.  
  2324.     if (!$mode || ($mode =~ /^[cd]/i)) { 
  2325.     $xml .=
  2326.         "<driver id=\"driver/" . $dat->{'driver'} . "\">\n" .
  2327.         "  <name>" . $dat->{'driver'} . "</name>\n" .
  2328.         "  <execution>\n" .
  2329.         "    <filter />\n" .
  2330.         "    <prototype>" . $dat->{'cmd'} . "</prototype>\n" .
  2331.         $dat->{'cmd_pdf'} ? 
  2332.         "    <prototype_pdf>" . $dat->{'cmd_pdf'} . "</prototype_pdf>\n" :
  2333.         "" .
  2334.         "  </execution>\n" .
  2335.         "</driver>\n\n";
  2336.     }
  2337.  
  2338.     if (!$mode || ($mode =~ /^c/i)) { 
  2339.     $xml .= "<options>\n";
  2340.  
  2341.     foreach (@{$dat->{'args'}}) {
  2342.         my $type = $_->{'type'};
  2343.         my $optname = $_->{'name'};
  2344.         $xml .= "  <option type=\"$type\" " .
  2345.         "id=\"opt/" . $dat->{'driver'} . "-" . $optname . "\">\n";
  2346.         $xml .=
  2347.         "    <arg_longname>\n" .
  2348.         "      <en>" . $_->{'comment'} . "</en>\n" .
  2349.         "    </arg_longname>\n" .
  2350.         "    <arg_shortname>\n" .
  2351.         "      <en>" . $_->{'name'} . "</en>\n" .
  2352.         "    </arg_shortname>\n" .
  2353.         "    <arg_execution>\n";
  2354.         $xml .= "      <arg_group>" . $_->{'group'} . "</arg_group>\n"
  2355.         if $_->{'group'};
  2356.         $xml .= "      <arg_order>" . $_->{'order'} . "</arg_order>\n"
  2357.         if $_->{'order'};
  2358.         $xml .= "      <arg_spot>" . $_->{'spot'} . "</arg_spot>\n"
  2359.         if $_->{'spot'};
  2360.         $xml .= "      <arg_proto>" . $_->{'proto'} . "</arg_proto>\n"
  2361.         if $_->{'proto'};
  2362.         $xml .= "    </arg_execution>\n";
  2363.         
  2364.         if ($type eq 'enum') {
  2365.         $xml .= "    <enum_vals>\n";
  2366.         my $vals_byname = $_->{'vals_byname'};
  2367.         foreach (keys(%{$vals_byname})) {
  2368.             my $val = $vals_byname->{$_};
  2369.             $xml .=
  2370.             "      <enum_val id=\"ev/" . $dat->{'driver'} . "-" .
  2371.             $optname . "-" . $_ . "\">\n";
  2372.             $xml .=
  2373.             "        <ev_longname>\n" .
  2374.             "          <en>" . $val->{'comment'} . "</en>\n" .
  2375.             "        </ev_longname>\n" .
  2376.             "        <ev_shortname>\n" .
  2377.             "          <en>$_</en>\n" .
  2378.             "        </ev_shortname>\n";
  2379.  
  2380.             $xml .=
  2381.             "        <ev_driverval>" .
  2382.             $val->{'driverval'} .
  2383.             "</ev_driverval>\n" if $val->{'driverval'};
  2384.  
  2385.             $xml .= "      </enum_val>\n";
  2386.         }
  2387.         $xml .= "    </enum_vals>\n";
  2388.         }
  2389.  
  2390.         $xml .= "  </option>\n";
  2391.     }
  2392.  
  2393.     $xml .= "</options>\n";
  2394.     $xml .= "</foomatic>\n";
  2395.     }
  2396.     return $xml;
  2397. }
  2398.  
  2399. sub ppdgetdefaults {
  2400.  
  2401.     # Read a PPD and get only the defaults and the postpipe.
  2402.     my ($this, $ppdfile) = @_;
  2403.  
  2404.     # Open the PPD file
  2405.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  2406.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or 
  2407.            die ("Unable to open PPD file \'$ppdfile\'\n");
  2408.  
  2409.     # We don't read the "COMDATA" lines of old Foomatic 2.0.x PPD files
  2410.     # here, because the defaults in the main PPD structure have priority.
  2411.     while(<PPD>) {
  2412.     # Foomatic should also work with PPD file downloaded under
  2413.     # Windows.
  2414.     $_ = undossify($_);
  2415.     # Parse keywords
  2416.     if (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  2417.         # "*FoomaticRIPPostPipe: <code>"
  2418.         my $line = $1;
  2419.         # Store the value
  2420.         # Code string can have multiple lines, read all of them
  2421.         my $cmd = "";
  2422.         while ($line !~ m!\"!) {
  2423.         if ($line =~ m!&&$!) {
  2424.             # line continues in next line
  2425.             $cmd .= substr($line, 0, -2);
  2426.         } else {
  2427.             # line ends here
  2428.             $cmd .= "$line\n";
  2429.         }
  2430.         # Read next line
  2431.         $line = <PPD>;
  2432.         chomp $line;
  2433.         }
  2434.         $line =~ m!^([^\"]*)\"!;
  2435.         $cmd .= $1;
  2436.         $this->{'dat'}{'postpipe'} = unhtmlify($cmd);
  2437.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  2438.         # "*Default<option>: <value>"
  2439.         my $argname = $1;
  2440.         my $default = $2;
  2441.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  2442.         # Store the value
  2443.         $this->{'dat'}{'args_byname'}{$argname}{'default'} =
  2444.             $default;
  2445.         }
  2446.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  2447.         # "*FoomaticRIPDefault<option>: <value>"
  2448.         # Used for numerical options only
  2449.         my $argname = $1;
  2450.         my $default = $2;
  2451.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  2452.         # Store the value
  2453.         $this->{'dat'}{'args_byname'}{$argname}{'fdefault'} =
  2454.             $default;
  2455.         }
  2456.     }
  2457.     }
  2458.  
  2459.     close PPD;
  2460.  
  2461.     # Set the defaults for the numerical options, taking into account
  2462.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  2463.     #  similar to other places in the code
  2464.     numericaldefaults($this->{'dat'}); 
  2465.  
  2466. }
  2467.  
  2468. sub ppdvarsetdefaults {
  2469.  
  2470.     my ($this, @ppdlinesin) = @_;
  2471.  
  2472.     my @ppdlines;
  2473.     my $ppd;
  2474.  
  2475.     for (my $i = 0; $i < @ppdlinesin; $i ++) {
  2476.     my $line = $ppdlinesin[$i];
  2477.     # Remove a postpipe definition if one is there
  2478.     if ($line =~ m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  2479.         # "*FoomaticRIPPostPipe: <code>"
  2480.         # Code string can have multiple lines, read all of them
  2481.         $line = $1;
  2482.         while ($line !~ m!\"!) {
  2483.         # Read next line
  2484.         $i++;
  2485.         $line = $ppdlinesin[$i];
  2486.         }
  2487.         # We also have to remove the "*End" line
  2488.         $i++;
  2489.         $line = $ppdlinesin[$i];
  2490.         if ($line !~ /^\*End/) {
  2491.         push(@ppdlines, $line);
  2492.         }
  2493.     } else {
  2494.         push(@ppdlines, $line);
  2495.     }
  2496.     }
  2497.     $ppd = join('', @ppdlines);
  2498.     # No option info read yet? Do not try to set deafaults
  2499.     return $ppd if !$this->{'dat'}{'args'};
  2500.  
  2501.     # If the settings for "PageSize" and "PageRegion" are different,
  2502.     # set the one for "PageRegion" to the one for "PageSize".
  2503.     if ($this->{'dat'}{'args_byname'}{'PageSize'}{'default'} ne
  2504.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'}) {
  2505.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'} =
  2506.         $this->{'dat'}{'args_byname'}{'PageSize'}{'default'}
  2507.     }
  2508.  
  2509.     # Numerical options: Set the "classical" default values
  2510.     # ("*Default<option>: <value>") to the value enumerated in the
  2511.     # list which is closest to the current default value.
  2512.     setnumericaldefaults($this->{'dat'}); 
  2513.  
  2514.     # Set the defaults in the PPD file to the current default
  2515.     # settings in the data structure
  2516.     for my $arg (@{$this->{'dat'}{'args'}}) {
  2517.     if (defined($arg->{'default'})) {
  2518.         my $name = $arg->{'name'};
  2519.         my $def = $arg->{'default'};
  2520.         if ($arg->{'type'} eq 'bool') {
  2521.         if ((lc($def) eq '1')   || (lc($def) eq 'on') || 
  2522.             (lc($def) eq 'yes') || (lc($def) eq 'true')) {
  2523.             $def='True';
  2524.         } elsif ((lc($def) eq '0')  || (lc($def) eq 'off') || 
  2525.              (lc($def) eq 'no') || (lc($def) eq 'false')) {
  2526.             $def='False';
  2527.         }
  2528.         $def = (checkoptionvalue($this->{'dat'}, $name, $def, 1) ?
  2529.             'True' : 'False');
  2530.         } elsif ($arg->{'type'} =~ /^(int|float)$/) {
  2531.         if (defined($arg->{'cdefault'})) {
  2532.             $def = $arg->{'cdefault'};
  2533.             undef $arg->{'cdefault'};
  2534.         }
  2535.         my $fdef = $arg->{'default'};
  2536.         $fdef = checkoptionvalue($this->{'dat'}, $name, $fdef, 1);
  2537.         $ppd =~ s!^(\*FoomaticRIPDefault$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$fdef$3!m;
  2538.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  2539.         } elsif ($arg->{'type'} =~ /^(string|password)$/) {
  2540.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  2541.         # An empty string cannot be an option name in a PPD file,
  2542.         # use "None" in this case, also substitute non-word characters
  2543.         # in the string to get a legal option name
  2544.         my $defcom = $def;
  2545.         my $defstr = $def;
  2546.         if ($def !~ /\S/) {
  2547.             $def = 'None';
  2548.             $defcom = '(None)';
  2549.             $defstr = '';
  2550.         } elsif ($def eq 'None') {
  2551.             $defcom = '(None)';
  2552.             $defstr = '';
  2553.         } else {
  2554.             $def =~ s/\W+/_/g;
  2555.             $def =~ s/^_+|_+$//g;
  2556.             $def = '_' if ($def eq '');
  2557.             $defcom =~ s/:/ /g;
  2558.             $defcom =~ s/^ +| +$//g;
  2559.         }
  2560.         # The default string is not available as an enumerated choice
  2561.         # ...
  2562.         if (($ppd !~ m!^\s*\*$arg->{name}\s+${def}[/:]!m) &&
  2563.             ($ppd !~ m!^\s*\*FoomaticRIPOptionSetting\s+$arg->{name}=${def}:!m)) {
  2564.             # ... build an appropriate PPD entry ...
  2565.             my $sprintfproto = $arg->{'proto'};
  2566.             $sprintfproto =~ s/\%(?!s)/\%\%/g;
  2567.             my $driverval = sprintf($sprintfproto, $defstr);
  2568.             my ($choicedef, $fchoicedef);
  2569.             if ($arg->{'style'} eq 'G') { # PostScript option
  2570.             $choicedef = sprintf("*%s %s/%s: \"%s\"", 
  2571.                          $name, $def, $defcom, $driverval);
  2572.             } else {
  2573.             my $header = sprintf
  2574.                 ("*FoomaticRIPOptionSetting %s=%s", $name, $def);
  2575.             $fchoicedef = ripdirective($header, $driverval); 
  2576.             if ($#{$arg->{'vals'}} >= 0) { # Visible non-PS option
  2577.                 $choicedef =
  2578.                 sprintf("*%s %s/%s: " .
  2579.                     "\"%%%% FoomaticRIPOptionSetting " .
  2580.                     "%s=%s\"", 
  2581.                     $name, $def, $defcom, $name, $def);
  2582.             }
  2583.             }
  2584.             if ($choicedef =~ /\n/s) {
  2585.             $choicedef .= "\n*End";
  2586.             }
  2587.             if ($fchoicedef =~ /\n/s) {
  2588.             $fchoicedef .= "\n*End";
  2589.             }
  2590.             if ($#{$arg->{'vals'}} == 0) {
  2591.             # ... and if there is only one choice, replace the one 
  2592.             # choice
  2593.             $ppd =~ s!^\*$name\s+.*?\".*?\"(\r?\n?\*End)?$!$choicedef!sm;
  2594.             $ppd =~ s!^\*FoomaticRIPOptionSetting\s+$name=.*?\".*?\"(\r?\n?\*End)?$!$fchoicedef!sm;
  2595.             } else {
  2596.             # ... and if there is no choice or more than one
  2597.             # choice, add a new choice for the default
  2598.             my $entrystr = 
  2599.                 ($choicedef ? "\n$choicedef" : "") .
  2600.                 ($fchoicedef ? "\n$fchoicedef" : "");
  2601.             for my $l ("Default$name:.*",
  2602.                    "OrderDependency.*$name",
  2603.                    "FoomaticRIPOptionMaxLength\\s+$name:.*",
  2604.                    "FoomaticRIPOptionPrototype\\s+$name:.*",
  2605.                    "FoomaticRIPOption\\s+$name:.*") {
  2606.                 $ppd =~ s!^(\*$l)$!$1$entrystr!m and last;
  2607.             }
  2608.             }
  2609.         }
  2610.         } else {
  2611.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 0);
  2612.         }
  2613.         $ppd =~ s!^(\*Default$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$def$3!m
  2614.         if defined($def);
  2615.     }
  2616.     }
  2617.  
  2618.     # Update the postpipe
  2619.     if ($this->{'dat'}{'postpipe'}) {
  2620.     my $header = "*FoomaticRIPPostPipe";
  2621.     my $code = $this->{'dat'}{'postpipe'};
  2622.     my $postpipestr = ripdirective($header, $code) . "\n";
  2623.     if ($postpipestr =~ /\n.*\n/s) {
  2624.         $postpipestr .= "*End\n";
  2625.     }
  2626.     #$ppd =~ s/(\*PPD[^a-zA-Z0-9].*\n)/$1$postpipestr/s;
  2627.     $ppd =~ s/((\r\n|\n\r|\r|\n))/$1$postpipestr/s;
  2628.     }
  2629.     
  2630.     return $ppd;
  2631. }
  2632.  
  2633. sub ppdsetdefaults {
  2634.  
  2635.     my ($this, $ppdfile) = @_;
  2636.     
  2637.     # Load the complete PPD file into memory
  2638.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  2639.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or
  2640.            die ("Unable to open PPD file \'$ppdfile\'\n");
  2641.     my @ppdlines = <PPD>;
  2642.     close PPD;
  2643.  
  2644.     # Set the defaults
  2645.     my $ppd = $this->ppdvarsetdefaults(@ppdlines);
  2646.     
  2647.     # Write back the modified PPD file
  2648.     open PPD, ($ppdfile !~ /\.gz$/i ? "> $ppdfile" : 
  2649.            "| $sysdeps->{'gzip'} > \'$ppdfile\'") or
  2650.     die ("Unable to open PPD file \'$ppdfile\' for writing\n");
  2651.     print PPD $ppd;
  2652.     close PPD;
  2653.     
  2654. }
  2655.  
  2656. # Some helper functions for reading the PPD file
  2657.  
  2658. sub unhtmlify {
  2659.     # Replace HTML/XML entities by the original characters
  2660.     my $str = $_[0];
  2661.     $str =~ s/\'/\'/g;
  2662.     $str =~ s/\"/\"/g;
  2663.     $str =~ s/\>/\>/g;
  2664.     $str =~ s/\</\</g;
  2665.     $str =~ s/\&/\&/g;
  2666.     return $str;
  2667. }
  2668.  
  2669. sub unhexify {
  2670.     # Replace hex notation for unprintable characters in PPD files
  2671.     # by the actual characters ex: "<0A>" --> chr(hex("0A"))
  2672.     my ($input, $encoding) = @_;
  2673.     my $output = "";
  2674.     my $hexmode = 0;
  2675.     my $hexstring = "";
  2676.     my $encoded = "";
  2677.     for (my $i = 0; $i < length($input); $i ++) {
  2678.     my $c = substr($input, $i, 1);
  2679.     if ($hexmode) {
  2680.         if ($c eq ">") {
  2681.         # End of hex string
  2682.         $encoded = '';
  2683.         for (my $i=0; $i < length($hexstring); $i+=2) {
  2684.             $encoded .= chr(hex(substr($hexstring, $i, 2)));
  2685.         }
  2686.         $output .= decode($encoding, $encoded);
  2687.         $hexmode = 0;
  2688.         } elsif ($c =~ /^[0-9a-fA-F]$/) {
  2689.         # Hexadecimal digit, two of them give a character
  2690.         $hexstring .= $c; 
  2691.         }
  2692.     } else {
  2693.         if ($c eq "<") {
  2694.         # Beginning of hex string
  2695.         $hexmode = 1;
  2696.         $hexstring = "";
  2697.         } else {
  2698.         # Normal character
  2699.         $output .= $c;
  2700.         }
  2701.     }
  2702.     }
  2703.     return $output;
  2704. }
  2705.  
  2706. sub undossify {
  2707.     # Remove "dossy" line ends ("\r\n") from a string
  2708.     my ($str) = @_;
  2709.     $str = "" if( !defined($str) );
  2710.     $str =~ s/\r\n/\n/gs;
  2711.     $str =~ s/\r$//s;
  2712.     return $str;
  2713. }
  2714.  
  2715. sub checkarg {
  2716.     # Check if there is already an argument record $argname in $dat, if not,
  2717.     # create one
  2718.     my ($dat, $argname) = @_;
  2719.     return if defined($dat->{'args_byname'}{$argname});
  2720.     # argument record
  2721.     my $rec;
  2722.     $rec->{'name'} = $argname;
  2723.     # Insert record in 'args' array for browsing all arguments
  2724.     push(@{$dat->{'args'}}, $rec);
  2725.     # 'args_byname' hash for looking up arguments by name
  2726.     $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}];
  2727.     # Default execution style is 'G' (PostScript) since all arguments for
  2728.     # which we don't find "*Foomatic..." keywords are usual PostScript
  2729.     # options
  2730.     $dat->{'args_byname'}{$argname}{'style'} = 'G';
  2731.     # Default prototype for code to insert, used by enum options
  2732.     $dat->{'args_byname'}{$argname}{'proto'} = '%s';
  2733.     # Mark option as hidden by default, as options consisting of only Foomatic
  2734.     # keywords are hidden. As soon as the PPD parser finds a non-Foomatic
  2735.     # keyword, it removes this mark
  2736.     $dat->{'args_byname'}{$argname}{'hidden'} = 1;
  2737. }
  2738.  
  2739. sub checksetting {
  2740.     # Check if there is already a choice record $setting in the $argname
  2741.     # argument in $dat, if not, create one
  2742.     my ($dat, $argname, $setting) = @_;
  2743.     return if 
  2744.     defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting});
  2745.     # setting record
  2746.     my $rec;
  2747.     $rec->{'value'} = $setting;
  2748.     # Insert record in 'vals' array for browsing all settings
  2749.     push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec);
  2750.     # 'vals_byname' hash for looking up settings by name
  2751.     $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} = 
  2752.     $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}];
  2753. }
  2754.  
  2755. sub removearg {
  2756.     # remove the argument record $argname from $dat
  2757.     my ($dat, $argname) = @_;
  2758.     return if !defined($dat->{'args_byname'}{$argname});
  2759.     # Remove 'args_byname' hash for looking up arguments by name
  2760.     delete $dat->{'args_byname'}{$argname};
  2761.     # Remove argument itself
  2762.     for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
  2763.     if ($dat->{'args'}[$i]{'name'} eq $argname) {
  2764.         splice(@{$dat->{'args'}}, $i, 1);
  2765.         last;
  2766.     }
  2767.     }
  2768. }
  2769.  
  2770. sub booltoenum {
  2771.     # Turn the boolean argument $argname from $dat to an enumerated choice
  2772.     # equivalent to the original argument
  2773.     my ($dat, $argname) = @_;
  2774.     return if !defined($dat->{'args_byname'}{$argname});
  2775.     # Argument record
  2776.     my $arg = $dat->{'args_byname'}{$argname};
  2777.     # General settings
  2778.     $arg->{'type'} = 'enum';
  2779.     my $proto = $arg->{'proto'};
  2780.     $arg->{'proto'} = '%s';
  2781.     # Choice for 'true'
  2782.     if (!defined($arg->{'name_true'})) {
  2783.     $arg->{'name_true'} = $arg->{'name'};
  2784.     }
  2785.     checksetting($dat, $argname, 'true');
  2786.     my $truechoice = $arg->{'vals_byname'}{'true'};
  2787.     $truechoice->{'comment'} = longname($arg->{'name_true'});
  2788.     $truechoice->{'driverval'} = $proto;
  2789.     # Choice for 'false'
  2790.     if (!defined($arg->{'name_false'})) {
  2791.     $arg->{'name_false'} = "no$arg->{'name'}";
  2792.     }
  2793.     checksetting($dat, $argname, 'false');
  2794.     my $falsechoice = $arg->{'vals_byname'}{'false'};
  2795.     $falsechoice->{'comment'} = longname($arg->{'name_false'});
  2796.     $falsechoice->{'driverval'} = '';
  2797.     # Default value
  2798.     if ($arg->{'default'} eq '0') {
  2799.     $arg->{'default'} = 'false';
  2800.     } else {
  2801.     $arg->{'default'} = 'true';
  2802.     }
  2803. }
  2804.  
  2805. sub checkoptionvalue {
  2806.  
  2807.     ## This function checks whether a given value is valid for a given
  2808.     ## option. If yes, it returns a cleaned value (e. g. always 0 or 1
  2809.     ## for boolean options), otherwise "undef". If $forcevalue is set,
  2810.     ## we always determine a corrected value to insert (we never return
  2811.     ## "undef").
  2812.  
  2813.     # Is $value valid for the option named $argname?
  2814.     my ($dat, $argname, $value, $forcevalue) = @_;
  2815.  
  2816.     # Record for option $argname
  2817.     my $arg = $dat->{'args_byname'}{$argname};
  2818.  
  2819.     if ($arg->{'type'} eq 'bool') {
  2820.     if ((lc($value) eq 'true') ||
  2821.         (lc($value) eq 'on') ||
  2822.         (lc($value) eq 'yes') ||
  2823.         (lc($value) eq '1')) {
  2824.         return 1;
  2825.     } elsif ((lc($value) eq 'false') ||
  2826.          (lc($value) eq 'off') ||
  2827.          (lc($value) eq 'no') ||
  2828.          (lc($value) eq '0')) {
  2829.         return 0;
  2830.     } elsif ($forcevalue) {
  2831.         # This maps Unknown to mean False.  Good?  Bad?
  2832.         # It was done so in Foomatic 2.0.x, too.
  2833.         return 0;
  2834.     }
  2835.     } elsif ($arg->{'type'} eq 'enum') {
  2836.     if ($arg->{'vals_byname'}{$value}) {
  2837.         return $value;
  2838.     } elsif ((($arg->{'name'} eq "PageSize") ||
  2839.           ($arg->{'name'} eq "PageRegion")) &&
  2840.          (defined($arg->{'vals_byname'}{'Custom'})) &&
  2841.          ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) {
  2842.         # Custom paper size
  2843.         return $value;
  2844.     } elsif ($forcevalue) {
  2845.         # wtf!?  that's not a choice!
  2846.         # Return the first entry of the list
  2847.         my $firstentry = $arg->{'vals'}[0]{'value'};
  2848.         return $firstentry;
  2849.     }
  2850.     } elsif (($arg->{'type'} eq 'int') ||
  2851.          ($arg->{'type'} eq 'float')) {
  2852.     if (($value <= $arg->{'max'}) &&
  2853.         ($value >= $arg->{'min'})) {
  2854.         return $value;
  2855.     } elsif ($forcevalue) {
  2856.         my $newvalue;
  2857.         if ($value > $arg->{'max'}) {
  2858.         $newvalue = $arg->{'max'}
  2859.         } elsif ($value < $arg->{'min'}) {
  2860.         $newvalue = $arg->{'min'}
  2861.         }
  2862.         return $newvalue;
  2863.     }
  2864.     } elsif (($arg->{'type'} eq 'string') ||
  2865.          ($arg->{'type'} eq 'password')) {
  2866.     if (defined($arg->{'vals_byname'}{$value})) {
  2867.         return $value;
  2868.     } elsif (stringvalid($dat, $argname, $value)) {
  2869.         # Check whether the string is one of the enumerated choices
  2870.         my $sprintfproto = $arg->{'proto'};
  2871.         $sprintfproto =~ s/\%(?!s)/\%\%/g;
  2872.         my $driverval = sprintf($sprintfproto, $value);
  2873.         for my $val (@{$arg->{'vals'}}) {
  2874.         if (($val->{'driverval'} eq $driverval) ||
  2875.             ($val->{'driverval'} eq $value)) {
  2876.             return $val->{value};
  2877.         }
  2878.         }
  2879.         # No matching choice? Return the original string
  2880.         return $value;
  2881.     } elsif ($forcevalue) {
  2882.         my $str = substr($value, 0, $arg->{'maxlength'});
  2883.         if (stringvalid($dat, $argname, $str)) {
  2884.         return $str;
  2885.         } elsif ($#{$arg->{'vals'}} >= 0) {
  2886.         # First list item
  2887.         my $firstentry = $arg->{'vals'}[0]{'value'};
  2888.         return $firstentry;
  2889.         } else {
  2890.         # Empty string
  2891.         return 'None';
  2892.         }
  2893.     }
  2894.     }
  2895.     return undef;
  2896. }
  2897.  
  2898. sub stringvalid {
  2899.  
  2900.     ## Checks whether a user-supplied value for a string option is valid
  2901.     ## It must be within the length limit, should only contain allowed
  2902.     ## characters and match the given regexp
  2903.  
  2904.     # Option and string
  2905.     my ($dat, $argname, $value) = @_;
  2906.  
  2907.     my $arg = $dat->{'args_byname'}{$argname};
  2908.  
  2909.     # Maximum length
  2910.     return 0 if (defined($arg->{'maxlength'}) &&
  2911.          (length($value) > $arg->{'maxlength'}));
  2912.  
  2913.     # Allowed characters
  2914.     if ($arg->{'allowedchars'}) {
  2915.     my $chars = $arg->{'allowedchars'};
  2916.     $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  2917.     return 0 if $value !~ /^[$chars]*$/;
  2918.     }
  2919.  
  2920.     # Regular expression
  2921.     if ($arg->{'allowedregexp'}) {
  2922.     my $regexp = $arg->{'allowedregexp'};
  2923.     $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  2924.     return 0 if $value !~ /$regexp/;
  2925.     }
  2926.  
  2927.     # All checks passed
  2928.     return 1;
  2929. }
  2930.  
  2931. sub checkoptions {
  2932.  
  2933.     ## Let the values of a boolean option being 0 or 1 instead of
  2934.     ## "True" or "False", range-check the defaults of all options and
  2935.     ## issue warnings if the values are not valid
  2936.  
  2937.     # Option set to be examined
  2938.     my ($dat, $optionset) = @_;
  2939.  
  2940.     for my $arg (@{$dat->{'args'}}) {
  2941.     if (defined($arg->{$optionset})) {
  2942.         $arg->{$optionset} =
  2943.         checkoptionvalue
  2944.         ($dat, $arg->{'name'}, $arg->{$optionset}, 1);
  2945.     }
  2946.     }
  2947.  
  2948.     # If the settings for "PageSize" and "PageRegion" are different,
  2949.     # set the one for "PageRegion" to the one for "PageSize".
  2950.     if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne
  2951.     $dat->{'args_byname'}{'PageRegion'}{$optionset}) {
  2952.     $dat->{'args_byname'}{'PageRegion'}{$optionset} =
  2953.         $dat->{'args_byname'}{'PageSize'}{$optionset};
  2954.     }
  2955. }
  2956.  
  2957. # If the PageSize or PageRegion was changed, also change the other
  2958.  
  2959. sub syncpagesize {
  2960.     
  2961.     # Name and value of the option we set, and the option set where we
  2962.     # did the change
  2963.     my ($dat, $name, $value, $optionset) = @_;
  2964.  
  2965.     # Don't do anything if we were called with an option other than
  2966.     # "PageSize" or "PageRegion"
  2967.     return if (($name ne "PageSize") && ($name ne "PageRegion"));
  2968.     
  2969.     # Don't do anything if not both "PageSize" and "PageRegion" exist
  2970.     return if ((!defined($dat->{'args_byname'}{'PageSize'})) ||
  2971.            (!defined($dat->{'args_byname'}{'PageRegion'})));
  2972.     
  2973.     my $dest;
  2974.     
  2975.     # "PageSize" --> "PageRegion"
  2976.     if ($name eq "PageSize") {
  2977.     $dest = "PageRegion";
  2978.     }
  2979.     
  2980.     # "PageRegion" --> "PageSize"
  2981.     if ($name eq "PageRegion") {
  2982.     $dest = "PageSize";
  2983.     }
  2984.     
  2985.     # Do it!
  2986.     my $val;
  2987.     if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) {
  2988.     # Standard paper size
  2989.     $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'};
  2990.     } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) {
  2991.     # Custom paper size
  2992.     $dat->{'args_byname'}{$dest}{$optionset} = $value;
  2993.     }
  2994. }
  2995.  
  2996. sub sortoptions {
  2997.  
  2998.     my ($dat, $only_options) = @_;
  2999.  
  3000.     # The following stuff is very awkward to implement in C, so we do
  3001.     # it here.
  3002.  
  3003.     # Sort options with "sortargs" function
  3004.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  3005.     @{$dat->{'args'}} = @sortedarglist;
  3006.  
  3007.     return if $only_options;
  3008.  
  3009.     # Sort values of enumerated options with "sortvals" function
  3010.     for my $arg (@{$dat->{'args'}}) {
  3011.     next if $arg->{'type'} !~ /^(enum|string|password)$/;
  3012.            my @sortedvalslist = sort sortvals keys(%{$arg->{'vals_byname'}});
  3013.     @{$arg->{'vals'}} = ();
  3014.     for my $i (@sortedvalslist) {
  3015.         my $val = $arg->{'vals_byname'}{$i};
  3016.         push (@{$arg->{'vals'}}, $val);
  3017.     }
  3018.     }
  3019.  
  3020. }
  3021.  
  3022. sub numericaldefaults {
  3023.  
  3024.     my ($dat) = @_;
  3025.  
  3026.     # Adobe's PPD specs do not support numerical
  3027.     # options. Therefore the numerical options are mapped to
  3028.     # enumerated options in the PPD file and their characteristics
  3029.     # as a numerical option are stored in "*Foomatic..."
  3030.     # keywords. Especially a default value between the enumerated
  3031.     # fixed values can be used as the default value. Then this
  3032.     # value must be given by a "*FoomaticRIPDefault<option>:
  3033.     # <value>" line in the PPD file. But this value is only valid,
  3034.     # if the "official" default given by a "*Default<option>:
  3035.     # <value>" line (it must be one of the enumerated values)
  3036.     # points to the enumerated value which is closest to this
  3037.     # value. This way a user can select a default value with a
  3038.     # tool only supporting PPD files but not Foomatic extensions.
  3039.     # This tool only modifies the "*Default<option>: <value>" line
  3040.     # and if the "*FoomaticRIPDefault<option>: <value>" had always
  3041.     # priority, the user's change in "*Default<option>: <value>"
  3042.     # had no effect.
  3043.  
  3044.     for my $arg (@{$dat->{'args'}}) {
  3045.     if ($arg->{'fdefault'}) {
  3046.         if ($arg->{'default'}) {
  3047.         if ($arg->{'type'} =~ /^(int|float)$/) {
  3048.             if ($arg->{'fdefault'} < $arg->{'min'}) {
  3049.             $arg->{'fdefault'} = $arg->{'min'};
  3050.             }
  3051.             if ($arg->{'fdefault'} > $arg->{'max'}) {
  3052.             $arg->{'fdefault'} = $arg->{'max'};
  3053.             }
  3054.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  3055.             my $closestvalue;
  3056.             for my $val (@{$arg->{'vals'}}) {
  3057.             if (abs($arg->{'fdefault'} - $val->{'value'}) <
  3058.                 $mindiff) {
  3059.                 $mindiff = 
  3060.                 abs($arg->{'fdefault'} - $val->{'value'});
  3061.                 $closestvalue = $val->{'value'};
  3062.             }
  3063.             }
  3064.             if (($arg->{'default'} == $closestvalue) ||
  3065.             (abs($arg->{'default'} - $closestvalue) /
  3066.              $closestvalue < 0.001)) {
  3067.             $arg->{'default'} = $arg->{'fdefault'};
  3068.             }
  3069.         }
  3070.         } else {
  3071.         $arg->{'default'} = $arg->{'fdefault'};
  3072.         }
  3073.     }
  3074.     }
  3075. }
  3076.  
  3077. sub setnumericaldefaults {
  3078.  
  3079.     my ($dat) = @_;
  3080.  
  3081.     for my $arg (@{$dat->{'args'}}) {
  3082.     if ($arg->{'default'}) {
  3083.         if ($arg->{'type'} =~ /^(int|float)$/) {
  3084.         if ($arg->{'default'} < $arg->{'min'}) {
  3085.             $arg->{'default'} = $arg->{'min'};
  3086.             $arg->{'cdefault'} = $arg->{'default'};
  3087.         } elsif ($arg->{'default'} > $arg->{'max'}) {
  3088.             $arg->{'default'} = $arg->{'max'};
  3089.             $arg->{'cdefault'} = $arg->{'default'};
  3090.         } elsif (defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  3091.             $arg->{'cdefault'} = $arg->{'default'};
  3092.         } else {
  3093.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  3094.             my $closestvalue;
  3095.             for my $val (@{$arg->{'vals'}}) {
  3096.             if (abs($arg->{'default'} - $val->{'value'}) <
  3097.                 $mindiff) {
  3098.                 $mindiff = 
  3099.                 abs($arg->{'default'} - $val->{'value'});
  3100.                 $closestvalue = $val->{'value'};
  3101.             }
  3102.             }
  3103.             $arg->{'cdefault'} = $closestvalue;
  3104.         }
  3105.         }
  3106.     }
  3107.     }
  3108.  
  3109. }
  3110.  
  3111. sub generalentries {
  3112.  
  3113.     my ($dat) = @_;
  3114.  
  3115.     $dat->{'compiled-at'} = localtime(time());
  3116.     $dat->{'timestamp'} = time();
  3117.  
  3118.     my $user = `whoami`; chomp $user;
  3119.     my $host = `hostname`; chomp $host;
  3120.  
  3121.     $dat->{'compiled-by'} = "$user\@$host";
  3122.  
  3123. }
  3124.  
  3125. sub checklongnames {
  3126.  
  3127.     my ($dat) = @_;
  3128.  
  3129.     # Add missing longnames/translations
  3130.     for my $arg (@{$dat->{'args'}}) {
  3131.     if (!($arg->{'comment'})) {
  3132.         $arg->{'comment'} = longname($arg->{'name'});
  3133.     }
  3134.     for my $i (@{$arg->{'vals'}}) {
  3135.         if (!($i->{'comment'})) {
  3136.         $i->{'comment'} = longname($i->{'value'});
  3137.         }
  3138.     }
  3139.     }
  3140. }
  3141.  
  3142. sub cutguiname {
  3143.     
  3144.     # If $shortgui is set and $str is longer than 39 characters, return the
  3145.     # first 39 characters of $str, otherwise the complete $str. 
  3146.  
  3147.     my ($str, $shortgui) = @_;
  3148.  
  3149.     if (($shortgui) && (length($str) > 39)) {
  3150.     return substr($str, 0, 39);
  3151.     } else {
  3152.     return $str;
  3153.     }
  3154. }
  3155.  
  3156. sub deviceIDfromDBEntry {
  3157.  
  3158.     my ($dat) = @_;
  3159.  
  3160.     # Complete IEEE 1284 ID string?
  3161.     my $ieee1284;
  3162.     $ieee1284 = $dat->{'general_ieee'} or $ieee1284 = $dat->{'pnp_ieee'} or
  3163.     $ieee1284 = $dat->{'par_ieee'} or $ieee1284 = $dat->{'usb_ieee'} or 
  3164.     $ieee1284 = $dat->{'snmp_ieee'} or $ieee1284 = "";
  3165.     # Extract data fields from the ID string
  3166.     my $ieeemake;
  3167.     my $ieeemodel;
  3168.     my $ieeecmd;
  3169.     my $ieeedes;
  3170.     if ($ieee1284) {
  3171.     $ieee1284 =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i;
  3172.     $ieeemake = $2;
  3173.     $ieee1284 =~ /(MDL|MODEL):\s*([^:;]+);?/i;
  3174.     $ieeemodel = $2;
  3175.     $ieee1284 =~ /(CMD|COMMANDS?\s*SET):\s*([^:;]+);?/i;
  3176.     $ieeecmd = $2;
  3177.     $ieee1284 =~ /(DES|DESCRIPTION):\s*([^:;]+);?/i;
  3178.     $ieeedes = $2;
  3179.     }
  3180.     # Auto-detection data listed field by field in the printer XML file?
  3181.     my $pnpmake;
  3182.     $pnpmake = $ieeemake or $pnpmake = $dat->{'general_mfg'} or 
  3183.     $pnpmake = $dat->{'pnp_mfg'} or $pnpmake = $dat->{'par_mfg'} or
  3184.     $pnpmake = $dat->{'usb_mfg'} or $pnpmake = "";
  3185.     my $pnpmodel;
  3186.     $pnpmodel = $ieeemodel or $pnpmodel = $dat->{'general_mdl'} or
  3187.     $pnpmodel = $dat->{'pnp_mdl'} or $pnpmodel = $dat->{'par_mdl'} or
  3188.     $pnpmodel = $dat->{'usb_mdl'} or $pnpmodel = "";
  3189.     my $pnpcmd;
  3190.     $pnpcmd = $ieeecmd or $pnpcmd = $dat->{'general_cmd'} or 
  3191.     $pnpcmd = $dat->{'pnp_cmd'} or $pnpcmd = $dat->{'par_cmd'} or
  3192.     $pnpcmd = $dat->{'usb_cmd'} or $pnpcmd = "";
  3193.     my $pnpdescription;
  3194.     $pnpdescription = $ieeedes or
  3195.     $pnpdescription = $dat->{'general_des'} or
  3196.     $pnpdescription = $dat->{'pnp_des'} or 
  3197.     $pnpdescription = $dat->{'par_des'} or
  3198.     $pnpdescription = $dat->{'usb_des'} or
  3199.     $pnpdescription = "";
  3200.     if ((!$ieee1284) && ((($pnpmake) && ($pnpmodel)) || ($pnpdescription))){
  3201.     $ieee1284 .= "MFG:$pnpmake;" if $pnpmake;
  3202.     $ieee1284 .= "MDL:$pnpmodel;" if $pnpmodel;
  3203.     $ieee1284 .= "CMD:$pnpcmd;" if $pnpcmd;
  3204.     $ieee1284 .= "DES:$pnpdescription;" if $pnpdescription;
  3205.     }
  3206.     return $ieee1284;
  3207. }
  3208.  
  3209. sub ppd1284DeviceID {
  3210.  
  3211.     # Clean up IEEE-1284 device ID to only contain the fields relevant
  3212.     # to printer model auto-detection (MFG, MDL, DES, CMD, SKU, DRV), thus
  3213.     # the line length limit of PPDs does not get exceeded on very long
  3214.     # ID strings.
  3215.  
  3216.     my ($id) = @_;
  3217.     my $ppdid = "";
  3218.     
  3219.     foreach my $field ("(MFG|MANUFACTURER)", "(MDL|MODEL)", "(CMD|COMMANDS?\\s*SET)", "(DES|DESCRIPTION)", "SKU", "DRV") {
  3220.     if ($id =~ m/(\b$field:\s*[^:;]+;?)/is) {
  3221.         my $f = $1;
  3222.         $ppdid .= ";" if $ppdid && $ppdid !~ /;$/;
  3223.         $ppdid .= $f;
  3224.     }
  3225.     }
  3226.  
  3227.     return $ppdid;
  3228. }
  3229.  
  3230. sub getppdheaderdata {
  3231.     
  3232.     my ($dat, $driver, $recdriver) = @_;
  3233.  
  3234.     my $ieee1284 = deviceIDfromDBEntry($dat);
  3235.  
  3236.     # Add driver profile to device ID string, so we get it into the
  3237.     # PPD listing output of CUPS
  3238.     my @profileitems = ();
  3239.     my $profileelements =
  3240.     [["manufacturersupplied", "M"],
  3241.      ["obsolete", "O"],
  3242.      ["free", "F"],
  3243.      ["patents", "P"],
  3244.      ["supportcontacts", "S"],
  3245.      ["type", "T"],
  3246.      ["drvmaxresx", "X"],
  3247.      ["drvmaxresy", "Y"],
  3248.      ["drvcolor", "C"],
  3249.      ["text", "t"],
  3250.      ["lineart", "l"],
  3251.      ["graphics", "g"],
  3252.      ["photo", "p"],
  3253.      ["load", "d"], 
  3254.      ["speed", "s"]];
  3255.     my $drvfield = '';
  3256.     foreach my $item (@{$profileelements}) {
  3257.     my ($perlkey, $devidkey) = @{$item};
  3258.     if ($perlkey eq "manufacturersupplied") {
  3259.         my $ms;
  3260.         if (defined($dat->{$perlkey})) {
  3261.         $ms = $dat->{$perlkey};
  3262.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  3263.         $ms = $dat->{'driverproperties'}{$driver}{$perlkey};
  3264.         }
  3265.         $drvfield .= "," . $devidkey .
  3266.         ($ms eq "1" ? "1" : ($dat->{make} =~ m,^($ms)$,i ? "1" : "0"));
  3267.     } elsif ($perlkey eq "supportcontacts") {
  3268.         my $sc;
  3269.         if (defined($dat->{$perlkey})) {
  3270.         $sc = $dat->{$perlkey};
  3271.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  3272.         $sc = $dat->{'driverproperties'}{$driver}{$perlkey};
  3273.         }
  3274.         if ($sc) {
  3275.         my $commercial = 0;
  3276.         my $voluntary = 0;
  3277.         my $unknown = 0;
  3278.         foreach my $entry (@{$sc}) {
  3279.             if ($entry->{'level'} =~ /^commercial$/i) {
  3280.             $commercial = 1;
  3281.             } elsif ($entry->{'level'} =~ /^voluntary$/i) {
  3282.             $voluntary = 1;
  3283.             } else {
  3284.             $unknown = 1;
  3285.             }
  3286.         }
  3287.         $drvfield .= "," . $devidkey . ($commercial ? "c" : "") .
  3288.             ($voluntary ? "v" : "") . ($unknown ? "u" : "");
  3289.         }
  3290.     } else {
  3291.         if (defined($dat->{$perlkey})) {
  3292.         $drvfield .= "," . $devidkey . $dat->{$perlkey};
  3293.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  3294.         $drvfield .= "," . $devidkey . 
  3295.             $dat->{'driverproperties'}{$driver}{$perlkey};
  3296.         }
  3297.     }
  3298.     }
  3299.     $ieee1284 .= ";" if $ieee1284 && $ieee1284 !~ /;$/;
  3300.     $ieee1284 .= "DRV:D$driver" .
  3301.     ($recdriver ? ($driver eq $recdriver ? ",R1" : ",R0") : "") .
  3302.     "$drvfield;";
  3303.  
  3304.     # Remove everything from the device ID which is not relevant to
  3305.     # auto-detection of the printer model.
  3306.     $ieee1284 = ppd1284DeviceID($ieee1284) if $ieee1284;
  3307.  
  3308.     my $make = $dat->{'make'};
  3309.     my $model = $dat->{'model'};
  3310.  
  3311.     $ieee1284 =~ /(MFG|MANUFACTURER):\s*([^:;]+);?/i;
  3312.     my $pnpmake = $2;
  3313.     $pnpmake = $make if !$pnpmake;
  3314.     $ieee1284 =~ /(MDL|MODEL):\s*([^:;]+);?/i;
  3315.     my $pnpmodel = $2;
  3316.     $pnpmodel = $model if (!$pnpmodel) || ($pnpmodel eq $pnpmake);
  3317.  
  3318.     # File name for the PPD file
  3319.     my $filename = join('-',($dat->{'make'},
  3320.                  $dat->{'model'},
  3321.                  $driver));;
  3322.     $filename =~ s![ /\(\)\,]!_!g;
  3323.     $filename =~ s![\+]!plus!g;
  3324.     $filename =~ s!__+!_!g;
  3325.     $filename =~ s!_$!!;
  3326.     $filename =~ s!^_!!;
  3327.     $filename =~ s!_-!-!;
  3328.     $filename =~ s!-_!-!;
  3329.     my $longname = "$filename.ppd";
  3330.  
  3331.     # Driver name
  3332.     my $drivername = $driver;
  3333.  
  3334.     # Do we use the recommended driver?
  3335.     my $driverrecommended = "";
  3336.     if ($driver eq $recdriver) {
  3337.     $driverrecommended = " (recommended)";
  3338.     }
  3339.     
  3340.     # evil special case.
  3341.     $drivername = "stp-4.0" if $drivername eq 'stp';
  3342.  
  3343.     # Nickname for the PPD file
  3344.     my $nickname =
  3345.     "$make $model Foomatic/$drivername$driverrecommended";
  3346.     my $modelname = "$make $model";
  3347.     # Remove forbidden characters (Adobe PPD spec 4.3 section 5.3)
  3348.     $modelname =~ s/[^A-Za-z0-9 \.\/\-\+]//gs;
  3349.  
  3350.     return ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  3351.         $drivername,$nickname,$modelname);
  3352. }
  3353.  
  3354. #
  3355. # PPD generation
  3356. #
  3357.  
  3358. # member( $a, @b ) returns 1 if $a is in @b, 0 otherwise.
  3359. sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 };
  3360.  
  3361.  
  3362. sub setgroupandorder {
  3363.  
  3364.     # Set group of member options. Make also sure that the composite
  3365.     # option will be inserted into the PostScript code before all its
  3366.     # # members are inserted (by means of the section and the order #
  3367.     # number).
  3368.  
  3369.     # The composite option to be treated ($arg)
  3370.     my ($db, $arg, $members_in_subgroup) = @_;
  3371.     
  3372.     # The Perl data structure of the current printer/driver combo.
  3373.     my $dat = $db->{'dat'};
  3374.  
  3375.     # Here we are only interested in composite options, skip the others
  3376.     return if $arg->{'style'} ne 'X';
  3377.  
  3378.     my $name = $arg->{'name'};
  3379.     my $group = $arg->{'group'};
  3380.     my $order = $arg->{'order'};
  3381.     my $section = $arg->{'section'};
  3382.     my @members = @{$arg->{'members'}};
  3383.  
  3384.     for my $m (@members) {
  3385.     my $a = $dat->{'args_byname'}{$m};
  3386.  
  3387.     # If $members_in_subgroup is set, the group should be a
  3388.     # subgroup of the group where the composite option is
  3389.     # located, named as the composite option. Otherwise the
  3390.     # group will get a new main group.
  3391.     if (($members_in_subgroup) && ($group)) {
  3392.         $a->{'group'} = "$group/$name";
  3393.     } else {
  3394.         $a->{'group'} = "$name";
  3395.     }
  3396.  
  3397.     # If the member is composite, call this function on it
  3398.     # recursively.  This sets the groups of the members of this
  3399.     # composite member option and also sets the section and order
  3400.     # number of this composite member, so that we can set section
  3401.     # and order of the currently treated option
  3402.     $db->setgroupandorder($a, $members_in_subgroup)
  3403.         if $a->{'style'} eq 'X';
  3404.  
  3405.     # Determine section and order number for the composite option
  3406.     # Order of the DSC sections of a PostScript file
  3407.     my @sectionorder = ("JCLSetup", "Prolog", "DocumentSetup", 
  3408.                 "AnySetup", "PageSetup");
  3409.  
  3410.     # Set default for missing section value in member
  3411.     if (!defined($a->{'section'})) {$a->{'section'} = "AnySetup";}
  3412.     my $minsection;
  3413.     for my $s (@sectionorder) {
  3414.         if (($s eq $arg->{'section'}) || ($s eq $a->{'section'})) {
  3415.         $minsection = $s;
  3416.         last;
  3417.         }
  3418.     }
  3419.  
  3420.     # If the current member option is in an earlier section,
  3421.     # put also the composite option into it. Do never put the
  3422.     # composite option into the JCL setup because in the JCL
  3423.     # header PostScript comments are not allowed.
  3424.     $arg->{'section'} = ($minsection ne "JCLSetup" ?
  3425.                  $minsection : "Prolog");
  3426.  
  3427.     # Let the order number of the composite option be less
  3428.     # than the order number of the current member
  3429.     if ($arg->{'order'} >= $a->{'order'}) {
  3430.         $arg->{'order'} = $a->{'order'} - 1;
  3431.         if ($arg->{'order'} < 0) {
  3432.         $arg->{'order'} = 0;
  3433.         }
  3434.     }
  3435.     }
  3436. }
  3437.  
  3438.  
  3439. # Return a generic Adobe-compliant PPD for the "foomatic-rip" filter script
  3440. # for all spoolers.  Built from the standard data; you must call getdat()
  3441. # first.
  3442. sub getppd (  $ $ $ ) {
  3443.  
  3444.     # If $shortgui is set, all GUI strings ("translations" in PPD
  3445.     # files) will be cut to a maximum length of 39 characters. This is
  3446.     # needed by the current (as of July 2003) version of the CUPS
  3447.     # PostScript driver for Windows.
  3448.  
  3449.     # If $members_in_subgroup is set, the member options of a composite
  3450.     # option go into a subgroup of the group where the composite option
  3451.     # is located. Otherwise the member options go into a new main group
  3452.  
  3453.     my ($db, $shortgui, $members_in_subgroup) = @_;
  3454.  
  3455.     die "you need to call getdat first!\n" 
  3456.     if (!defined($db->{'dat'}));
  3457.  
  3458.     # The Perl data structure of the current printer/driver combo.
  3459.     my $dat = $db->{'dat'};
  3460.  
  3461.     # Do we have a custom pre-made PPD? If so, return this one
  3462.     if (defined($dat->{'ppdfile'})) {
  3463.     my $ppdfile = $dat->{'ppdfile'};
  3464.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  3465.     if (-r $ppdfile) {
  3466.         # Load the complete PPD file into memory
  3467.         if (open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  3468.                "$sysdeps->{'gzip'} -cd \'$ppdfile\' |")) {
  3469.         my @ppdlines = <PPD>;
  3470.         close PPD;
  3471.         # Set the default values
  3472.         my $ppd = $db->ppdvarsetdefaults(@ppdlines);
  3473.         return $ppd;
  3474.         }
  3475.     }
  3476.     }
  3477.  
  3478.     my @optionblob; # Lines for command line and options in the PPD file
  3479.  
  3480.     # Insert the printer/driver IDs and the command line prototype
  3481.     # right before the option descriptions
  3482.  
  3483.     push(@optionblob, "*FoomaticIDs: $dat->{'id'} $dat->{'driver'}\n");
  3484.     my $header = "*FoomaticRIPCommandLine";
  3485.     my $cmdline = $dat->{'cmd'};
  3486.     my $cmdlinestr = ripdirective($header, $cmdline);
  3487.     if ($cmdline) {
  3488.     # Insert the "*FoomaticRIPCommandLine" directive, but only if
  3489.     # the command line prototype is not empty
  3490.     push(@optionblob, "$cmdlinestr\n");
  3491.     if ($cmdlinestr =~ /\n/s) {
  3492.         push(@optionblob, "*End\n");
  3493.     }
  3494.     }
  3495.     $header = "*FoomaticRIPCommandLinePDF";
  3496.     $cmdline = $dat->{'cmd_pdf'};
  3497.     $cmdlinestr = ripdirective($header, $cmdline);
  3498.     if ($cmdline) {
  3499.     # Insert the "*FoomaticRIPCommandLine" directive, but only if
  3500.     # the command line prototype is not empty
  3501.     push(@optionblob, "$cmdlinestr\n");
  3502.     if ($cmdlinestr =~ /\n/s) {
  3503.         push(@optionblob, "*End\n");
  3504.     }
  3505.     }
  3506.     if ($dat->{'drivernopageaccounting'}) {
  3507.     push(@optionblob, "*FoomaticRIPNoPageAccounting: True\n");
  3508.     }
  3509.  
  3510.     # Search for composite options and prepare the member options
  3511.     # of the found composite options
  3512.     for my $arg (@{$dat->{'args'}}) {
  3513.     # Here we are only interested in composite options, skip the others
  3514.     next if $arg->{'style'} ne 'X';
  3515.     my $name = $arg->{'name'};
  3516.     my $com  = $arg->{'comment'};
  3517.     my $group = $arg->{'group'};
  3518.     my $order = $arg->{'order'};
  3519.     my $section = $arg->{'section'};
  3520.  
  3521.     # The "PageRegion" option is generated automatically, so ignore an
  3522.     # already existing "PageRegion". 
  3523.     next if $name eq "PageRegion";
  3524.  
  3525.     # Set default for missing section value
  3526.     if (!defined($section)) {$arg->{'section'} = "AnySetup";}
  3527.  
  3528.     # Set default for missing tranaslation/longname
  3529.     if (!$com) {$com = longname($name);}
  3530.  
  3531.     my @members;
  3532.  
  3533.     # Go through all choices of the composite option to find its
  3534.     # member options
  3535.     for my $v (@{$arg->{'vals'}}) {
  3536.         my @settings = split(/\s+/s, $v->{'driverval'});
  3537.         for my $s (@settings) {
  3538.         if (($s =~ /^([^=]+)=/) ||
  3539.             ($s =~ /^[Nn][Oo]([^=]+)$/) ||
  3540.             ($s =~ /^([^=]+)$/)) {
  3541.             my $m = $1;
  3542.             # Does the found member exist for this printer/driver
  3543.             # combo?
  3544.             if (defined($dat->{'args_byname'}{$m})) {
  3545.             # Add it to the list of found member options
  3546.             if (!member($m, @members)) {
  3547.                 push(@members, $1);
  3548.             }
  3549.             # Clean up entries for boolean options
  3550.             if ($s !~ /=/) {
  3551.                 if ($s =~ /^[Nn][Oo]$m$/) {
  3552.                 $v->{'driverval'} =~
  3553.                     s/(^|\s)$s($|\s)/$1$m=false$2/;
  3554.                 } else {
  3555.                 $v->{'driverval'} =~ 
  3556.                     s/(^|\s)$s($|\s)/$1$m=true$2/;
  3557.                 }
  3558.             }
  3559.             } else {
  3560.             # Remove it from the choice of the composite
  3561.             # option
  3562.             $v->{'driverval'} =~ s/$s\s*//;
  3563.             $v->{'driverval'} =~ s/\s*$//;
  3564.             }
  3565.         }
  3566.         }
  3567.     }
  3568.  
  3569.     # Add the member list to the data structure of the composite
  3570.     # option. We need it for the recursive setting of group names
  3571.     # and order numbers
  3572.     $arg->{'members'} = \@members;
  3573.  
  3574.     # Add a "From<Composite>" choice which will be the
  3575.     # default. Check also all members if they are hidden, if so,
  3576.     # this composite option is a forced composite option.
  3577.     my $nothiddenmemberfound = 0;
  3578.     for my $m (@members) {
  3579.         my $a = $dat->{'args_byname'}{$m};
  3580.  
  3581.         # Mark this member as being a member of the current
  3582.         # composite option
  3583.         $a->{'memberof'} = $name;
  3584.  
  3585.         # Convert boolean options to enumerated choice options, so
  3586.         # that we can add the "From<Composite>" choice.
  3587.         if ($a->{'type'} eq 'bool') {
  3588.         booltoenum($dat, $a->{'name'});
  3589.         }
  3590.  
  3591.         # Is this member option hidden?
  3592.         if (!$a->{'hidden'}) {
  3593.         $nothiddenmemberfound = 1;
  3594.         }
  3595.  
  3596.         # In case of a forced composite option mark the member option
  3597.         # as hidden.
  3598.         if (defined($arg->{'substyle'}) &&
  3599.         ($arg->{'substyle'} eq 'F')) {
  3600.         $a->{'hidden'} = 1;
  3601.         }
  3602.  
  3603.         # Do not add a "From<Composite>" choice to an option with only
  3604.         # one choice
  3605.         next if $#{$a->{'vals'}} < 1;
  3606.  
  3607.         if (!defined($a->{'vals_byname'}{"From$name"})) {
  3608.         # Add "From<Composite>" choice
  3609.         # setting record
  3610.         my $rec;
  3611.         $rec->{'value'} = "From$name";
  3612.         $rec->{'comment'} = "Controlled by '$com'";
  3613.         # We mark the driverval as invalid with a non-printable
  3614.         # character, this means that the code to insert will be an
  3615.         # empty string in the PPD.
  3616.         $rec->{'driverval'} = "\x01";
  3617.         # Insert record as the first item in the 'vals' array
  3618.         unshift(@{$a->{'vals'}}, $rec);
  3619.         # Update 'vals_byname' hash
  3620.         $a->{'vals_byname'}{$rec->{'value'}} = $a->{'vals'}[0];
  3621.         for (my $i = 1; $i <= $#{$a->{'vals'}}; $i ++) {
  3622.             $a->{'vals_byname'}{$a->{'vals'}[$i]{'value'}} =
  3623.             $a->{'vals'}[$i];
  3624.         }
  3625.         } else {
  3626.         # Only update the values
  3627.         $a->{'vals_byname'}{"From$name"}{'value'} = "From$name";
  3628.         $a->{'vals_byname'}{"From$name"}{'comment'} =
  3629.             "Controlled by '$com'";
  3630.         $a->{'vals_byname'}{"From$name"}{'driverval'} = "\x01";
  3631.         }
  3632.  
  3633.         # Set default to the new "From<Composite>" choice
  3634.         $a->{'default'} = "From$name";
  3635.     }
  3636.  
  3637.     # If all member options are hidden, this composite option is
  3638.     # a forced composite option and has to be marked appropriately
  3639.     if (!$nothiddenmemberfound) {
  3640.         $arg->{'substyle'} = 'F';
  3641.     }
  3642.     }
  3643.  
  3644.     # Now recursively set the groups and the order sections and numbers
  3645.     # for all composite options and their members.
  3646.     for my $arg (@{$dat->{'args'}}) {
  3647.     # The recursion should only be started in composite options
  3648.     # which are not member of another composite option.
  3649.     $db->setgroupandorder($arg, $members_in_subgroup) 
  3650.         if ($arg->{'style'} eq 'X') and (!$arg->{'memberof'});
  3651.     }
  3652.  
  3653.     # Sort options with "sortargs" function after they were re-grouped
  3654.     # due to the composite options
  3655.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  3656.     @{$dat->{'args'}} = @sortedarglist;
  3657.  
  3658.     # Construct the option entries for the PPD file
  3659.  
  3660.     my @groupstack; # In which group are we currently
  3661.  
  3662.     for my $arg (@{$dat->{'args'}}) {
  3663.     my $name = $arg->{'name'};
  3664.     my $type = $arg->{'type'};
  3665.     my $com  = $arg->{'comment'};
  3666.     my $default = $arg->{'default'};
  3667.     my $order = $arg->{'order'};
  3668.     my $spot = $arg->{'spot'};
  3669.     my $section = $arg->{'section'};
  3670.     my $cmd = $arg->{'proto'};
  3671.     my @group;
  3672.     @group = split("/", $arg->{'group'}) if defined($arg->{'group'});
  3673.     my $idx = $arg->{'idx'};
  3674.  
  3675.     # What is the execution style of the current option? Skip options
  3676.         # of unknown execution style
  3677.     my $optstyle = ($arg->{'style'} eq 'G' ? "PS" :
  3678.             ($arg->{'style'} eq 'J' ? "JCL" :
  3679.              ($arg->{'style'} eq 'C' ? "CmdLine" :
  3680.               ($arg->{'style'} eq 'X' ? "Composite" :
  3681.                "Unknown"))));
  3682.     next if $optstyle eq "Unknown";
  3683.  
  3684.     # The "PageRegion" option is generated automatically, so ignore an
  3685.     # already existing "PageRegion". 
  3686.     next if $name eq "PageRegion";
  3687.  
  3688.     # The command prototype should not be empty, set default
  3689.     if (!$cmd) {
  3690.         $cmd = "%s";
  3691.     }
  3692.  
  3693.     # Set default for missing section value
  3694.     if (defined($arg->{'style'}) && ($arg->{'style'} eq "J") &&
  3695.         !defined($arg->{'memberof'})) {
  3696.         $arg->{'section'} = "JCLSetup";
  3697.         } elsif (!defined($arg->{'section'})) {
  3698.         $arg->{'section'} = "AnySetup"
  3699.     }
  3700.     $section = $arg->{'section'};
  3701.  
  3702.     my $jcl = (($section eq 'JCLSetup') &&
  3703.            !defined($arg->{'memberof'}) ? "JCL" : "");
  3704.  
  3705.     # Set default for missing tranaslation/longname
  3706.     if (!$com) {$com = longname($name);}
  3707.  
  3708.     # If for a string option the default value is not available under
  3709.     # the enumerated choices, add it here. Make the default choice also
  3710.     # the first list entry
  3711.     if ($type =~ /^(string|password)$/) {
  3712.         $arg->{'default'} =
  3713.         checkoptionvalue($dat, $name, $arg->{'default'}, 1);
  3714.         # An empty string cannot be an option name in a PPD file,
  3715.         # use "None" in this case
  3716.         my $defcom = $arg->{'default'};
  3717.         my $defstr = $arg->{'default'};
  3718.         if ($arg->{'default'} !~ /\S/) {
  3719.         $arg->{'default'} = 'None';
  3720.         $defcom = '(None)';
  3721.         $defstr = '';
  3722.         } elsif ($arg->{'default'} eq 'None') {
  3723.         $defcom = '(None)';
  3724.         $defstr = '';
  3725.         } else {
  3726.         $arg->{'default'} =~ s/\W+/_/g;
  3727.         $arg->{'default'} =~ s/^_+|_+$//g;
  3728.         $arg->{'default'} = '_' if ($arg->{'default'} eq '');
  3729.             $defcom =~ s/:/ /g;
  3730.         $defcom =~ s/^ +| +$//g;
  3731.         }
  3732.         $default = $arg->{'default'};
  3733.         # Generate a new choice
  3734.         if (!defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  3735.         checksetting($dat, $name, $arg->{'default'});
  3736.         my $newchoice = $arg->{'vals_byname'}{$arg->{'default'}};
  3737.         $newchoice->{'value'} = $arg->{'default'};
  3738.         $newchoice->{'comment'} = $defcom;
  3739.         $newchoice->{'driverval'} = $defstr;
  3740.         }
  3741.         # Bring the default entry to the first position
  3742.         my $index = 0;
  3743.         for (my $i = 0; $i <= $#{$arg->{vals}}; $i ++) {
  3744.         if ($arg->{vals}[$i]{'value'} eq $arg->{'default'}) {
  3745.             $index = $i;
  3746.             last;
  3747.         }
  3748.         }
  3749.         my $def = splice(@{$arg->{vals}}, $index, 1);
  3750.         unshift(@{$arg->{vals}}, $def);
  3751.     }
  3752.  
  3753.     # Do we have to open or close one or more groups here?
  3754.     # No group will be opened more than once, since the options
  3755.     # are sorted to have the members of every group together
  3756.  
  3757.     # Only take into account the groups of options which will be
  3758.     # visible user interface options in the PPD.
  3759.     if ((($type !~ /^(enum|string|password)$/) ||
  3760.          ($#{$arg->{'vals'}} > 0) || ($name eq "PageSize") ||
  3761.          ($arg->{'style'} eq 'G')) &&
  3762.         (!$arg->{'hidden'})) {
  3763.         # Find the level on which the group path of the current option
  3764.         # (@group) differs from the group path of the last option
  3765.         # (@groupstack).
  3766.         my $level = 0;
  3767.         while (($level <= $#groupstack) and
  3768.            ($level <= $#group) and 
  3769.            ($groupstack[$level] eq $group[$level])) {
  3770.         $level++;
  3771.         }
  3772.         for (my $i = $#groupstack; $i >= $level; $i--) {
  3773.         # Close this group, the current option is not member
  3774.         # of it.
  3775.         push(@optionblob,
  3776.              sprintf("\n*Close%sGroup: %s\n",
  3777.                  ($i > 0 ? "Sub" : ""), $groupstack[$i])
  3778.              );
  3779.         pop(@groupstack);
  3780.         }
  3781.         for (my $i = $level; $i <= $#group; $i++) {
  3782.         # Open this group, the current option is a member
  3783.         # of it.
  3784.         push(@optionblob,
  3785.              sprintf("\n*Open%sGroup: %s/%s\n",
  3786.                  ($i > 0 ? "Sub" : ""), $group[$i], 
  3787.                  cutguiname(longname($group[$i]), $shortgui))
  3788.              );
  3789.         push(@groupstack, $group[$i]);
  3790.         }
  3791.     }
  3792.  
  3793.     if ($type =~ /^(enum|string|password)$/) {
  3794.         # Extra information for string options
  3795.         my ($stringextralines0, $stringextralines1) = ('', '');
  3796.         if ($type =~ /^(string|password)$/) {
  3797.         $stringextralines0 .= sprintf
  3798.              ("*FoomaticRIPOption %s: %s %s %s\n",
  3799.               $name, $type, $optstyle, $spot);
  3800.         my $header = sprintf
  3801.             ("*FoomaticRIPOptionPrototype %s",
  3802.              $name);
  3803.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3804.         $stringextralines1 .= $foomaticstr;
  3805.         # Stuff to insert into command line/job is more than one
  3806.         # line? Let an "*End" line follow
  3807.         if ($foomaticstr =~ /\n.*\n/s) {
  3808.             $stringextralines1 .= "*End\n";
  3809.         }
  3810.  
  3811.         if ($arg->{'maxlength'}) {
  3812.             $stringextralines1 .= sprintf
  3813.              ("*FoomaticRIPOptionMaxLength %s: %s\n",
  3814.               $name, $arg->{'maxlength'});
  3815.         }
  3816.  
  3817.         if ($arg->{'allowedchars'}) {
  3818.             my $header = sprintf
  3819.             ("*FoomaticRIPOptionAllowedChars %s",
  3820.              $name);
  3821.             my $entrystr = ripdirective($header, 
  3822.                         $arg->{'allowedchars'}) . "\n";
  3823.             $stringextralines1 .= $entrystr;
  3824.             # Stuff to insert into command line/job is more than one
  3825.             # line? Let an "*End" line follow
  3826.             if ($entrystr =~ /\n.*\n/s) {
  3827.             $stringextralines1 .= "*End\n";
  3828.             }
  3829.         }
  3830.  
  3831.         if ($arg->{'allowedregexp'}) {
  3832.             my $header = sprintf
  3833.             ("*FoomaticRIPOptionAllowedRegExp %s",
  3834.              $name);
  3835.             my $entrystr = ripdirective($header, 
  3836.                         $arg->{'allowedregexp'}) .
  3837.                             "\n";
  3838.             $stringextralines1 .= $entrystr;
  3839.             # Stuff to insert into command line/job is more than one
  3840.             # line? Let an "*End" line follow
  3841.             if ($entrystr =~ /\n.*\n/s) {
  3842.             $stringextralines1 .= "*End\n";
  3843.             }
  3844.         }
  3845.  
  3846.         }
  3847.  
  3848.         # Skip zero or one choice arguments. Do not skip "PageSize",
  3849.         # since a PPD file without "PageSize" will break the CUPS
  3850.         # environment and also do not skip PostScript options. For
  3851.         # skipped options with one choice only "*Foomatic..."
  3852.         # definitions will be used. Skip also the hidden member
  3853.         # options of a forced composite option.
  3854.         if (((1 < scalar(@{$arg->{'vals'}})) ||
  3855.          ($name eq "PageSize") ||
  3856.          ($arg->{'style'} eq 'G')) &&
  3857.         (!$arg->{'hidden'}) &&
  3858.         (0 < scalar(@{$arg->{'vals'}}))) {
  3859.  
  3860.         push(@optionblob,
  3861.              sprintf("\n*${jcl}OpenUI *%s/%s: PickOne\n", $name, 
  3862.                  cutguiname($com, $shortgui)));
  3863.  
  3864.         if ($arg->{'style'} ne 'G' && 
  3865.             (($optstyle ne "JCL") || defined($arg->{'memberof'}))) {
  3866.             # For non-PostScript options insert line with option
  3867.             # properties
  3868.             push(@optionblob, sprintf
  3869.              ("*FoomaticRIPOption %s: %s %s %s\n",
  3870.               $name, $type, $optstyle, $spot));
  3871.         }
  3872.  
  3873.         if ($type =~ /^(string|password)$/) {
  3874.             # Extra information for string options
  3875.             push(@optionblob, $stringextralines0, $stringextralines1);
  3876.         }
  3877.  
  3878.         push(@optionblob,
  3879.              sprintf("*OrderDependency: %s %s *%s\n", 
  3880.                  $order, $section, $name),
  3881.              sprintf("*Default%s: %s\n", 
  3882.                  $name,
  3883.                  (defined($default) ? 
  3884.                   checkoptionvalue($dat, $name, $default, 1) :
  3885.                   'Unknown')));
  3886.  
  3887.         if (!defined($default)) {
  3888.             my $whr = sprintf("%s %s driver %s",
  3889.                       $dat->{'make'},
  3890.                       $dat->{'model'},
  3891.                       $dat->{'driver'});
  3892.             warn "undefined default for $idx/$name on a $whr\n";
  3893.         }
  3894.         
  3895.         # If this is the page size argument; construct
  3896.         # PageRegion, ImageableArea, and PaperDimension clauses 
  3897.         # from it. Arguably this is all backwards, but what can
  3898.         # you do! ;)
  3899.         my @pageregion;
  3900.         my @imageablearea;
  3901.         my @paperdimension;
  3902.  
  3903.         # If we have a paper size named "Custom", or one with
  3904.         # one or both dimensions being zero, we must replace
  3905.         # this by an Adobe-complient custom paper size
  3906.         # definition.
  3907.         my $hascustompagesize = 0;
  3908.  
  3909.         # We take very big numbers now, to not impose limits.
  3910.         # Later, when we will have physical demensions of the
  3911.         # printers in the database.
  3912.         my $maxpagewidth = 100000;
  3913.         my $maxpageheight = 100000;
  3914.  
  3915.         # Start the PageRegion, ImageableArea, and PaperDimension
  3916.         # clauses
  3917.         if ($name eq "PageSize") {
  3918.             
  3919.             push(@pageregion,
  3920.              "*${jcl}OpenUI *PageRegion: PickOne
  3921. *OrderDependency: $order $section *PageRegion
  3922. *DefaultPageRegion: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3923.             push(@imageablearea, 
  3924.              "*DefaultImageableArea: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3925.             push(@paperdimension, 
  3926.              "*DefaultPaperDimension: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3927.         }
  3928.  
  3929.         for my $v (@{$arg->{'vals'}}) {
  3930.             my $psstr = "";
  3931.  
  3932.             if ($name eq "PageSize") {
  3933.             
  3934.             my $value = $v->{'value'}; # in a PPD, the value 
  3935.                                        # is the PPD name...
  3936.             my $comment = $v->{'comment'};
  3937.  
  3938.             # Here we have to fill in the absolute sizes of the 
  3939.             # papers. We consult a table when we could not read
  3940.             # the sizes out of the choices of the "PageSize"
  3941.             # option.
  3942.             my $size = $v->{'driverval'};
  3943.             if ($size =~ /([\d\.]+)x([\d\.]+)([a-z]+)\b/) {
  3944.                 # 2 positive integers separated by 
  3945.                 # an 'x' with a unit
  3946.                 my $w = $1;
  3947.                 my $h = $2;
  3948.                 my $u = $3;
  3949.                 if ($u =~ /^in(|ch(|es))$/i) {
  3950.                 $w *= 72.0;
  3951.                 $h *= 72.0;
  3952.                 } elsif ($u =~ /^mm$/i) {
  3953.                 $w *= 72.0/25.4;
  3954.                 $h *= 72.0/25.4;
  3955.                 } elsif ($u =~ /^cm$/i) {
  3956.                 $w *= 72.0/2.54;
  3957.                 $h *= 72.0/2.54;
  3958.                 }
  3959.                 $w = sprintf("%.2f", $w) if $w =~ /\./;
  3960.                 $h = sprintf("%.2f", $h) if $h =~ /\./;
  3961.                 $size = "$w $h";
  3962.             } elsif (($size =~ /(\d+)[x\s]+(\d+)/) ||
  3963.                 # 2 positive integers separated by 
  3964.                 # whitespace or an 'x'
  3965.                  ($size =~ /\-dDEVICEWIDTHPOINTS\=(\d+)\s+\-dDEVICEHEIGHTPOINTS\=(\d+)/)) {
  3966.                 # "-dDEVICEWIDTHPOINTS=..."/"-dDEVICEHEIGHTPOINTS=..."
  3967.                 $size = "$1 $2";
  3968.             } else {
  3969.                 $size = getpapersize($value);
  3970.             }
  3971.             $size =~ /^\s*([\d\.]+)\s+([\d\.]+)\s*$/;
  3972.             my $width = $1;
  3973.             my $height = $2;
  3974.             if ($maxpagewidth < $width) {
  3975.                 $maxpagewidth = $width;
  3976.             }
  3977.             if ($maxpageheight < $height) {
  3978.                 $maxpageheight = $height;
  3979.             }
  3980.             if (($value eq "Custom") ||
  3981.                 ($width == 0) || ($height == 0)) {
  3982.                 # This page size is either named "Custom" or
  3983.                 # at least one of its dimensions is not fixed
  3984.                 # (=0), so this printer/driver combo must
  3985.                 # support custom page sizes
  3986.                 $hascustompagesize = 1;
  3987.                 # We do not add this size to the PPD file
  3988.                 # because the Adobe standard foresees a
  3989.                 # special code block in the header of the
  3990.                 # PPD file to be inserted when a custom
  3991.                 # page size is requested.
  3992.                 next;
  3993.             }
  3994.             # Determine the unprintable margins
  3995.             # Zero margins when no margin info exists
  3996.             my ($left, $right, $top, $bottom) =
  3997.                 getmargins($dat, $width, $height, $value);
  3998.             # Insert margins in "*ImageableArea" line
  3999.             push(@imageablearea,
  4000.                  "*ImageableArea $value/$comment: " . 
  4001.                  "\"$left $bottom $right $top\"");
  4002.             push(@paperdimension,
  4003.                  "*PaperDimension $value/$comment: \"$size\"");
  4004.             }
  4005.             my $foomaticstr = "";
  4006.             # For PostScript options PostScript code must be 
  4007.             # inserted, unless they are member of a composite
  4008.             # option AND they are set to the "Controlled by
  4009.             # '<Composite>'" choice (driverval is "\x01")
  4010.             if (($arg->{'style'} eq 'G' || 
  4011.              (($optstyle eq "JCL") &&
  4012.               !defined($arg->{'memberof'}))) &&
  4013.             ($v->{'driverval'} ne "\x01")) {
  4014.             # Ghostscript argument; offer up ps for
  4015.             # insertion
  4016.             my $sprintfcmd = $cmd;
  4017.             if ($optstyle eq "JCL") {
  4018.                 if ($sprintfcmd !~ m/^@/) {
  4019.                 $sprintfcmd = "\@PJL " . $sprintfcmd;
  4020.                 }
  4021.                 if ($sprintfcmd !~ m/<0A>$/) {
  4022.                 $sprintfcmd = $sprintfcmd . "<0A>";
  4023.                 }
  4024.             }
  4025.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4026.             $psstr = sprintf($sprintfcmd, 
  4027.                      (defined($v->{'driverval'})
  4028.                       ? $v->{'driverval'}
  4029.                       : $v->{'value'}));
  4030.             } else {
  4031.             # Option setting directive for Foomatic filter
  4032.             # 4 "%" because of the "sprintf" applied to it
  4033.             # In the end stay 2 "%" to have a PostScript 
  4034.             # comment
  4035.             $psstr = sprintf
  4036.                 ("%%%% FoomaticRIPOptionSetting: %s=%s",
  4037.                  $name, $v->{'value'});
  4038.             if ($v->{'driverval'} eq "\x01") {
  4039.                 # Only set the $foomaticstr when the selected
  4040.                 # choice is not the "Controlled by
  4041.                 # '<Composite>'" of a member of a collective
  4042.                 # option. Otherwise leave it out and let
  4043.                 # the value in the "FoomaticRIPOptionSetting"
  4044.                 # comment be "@<Composite>".
  4045.                 $psstr =~ s/=From/=\@/;
  4046.                 $foomaticstr = "";
  4047.             } else {
  4048.                 my $header = sprintf
  4049.                 ("*FoomaticRIPOptionSetting %s=%s",
  4050.                  $name, $v->{'value'});
  4051.                 my $sprintfcmd = $cmd;
  4052.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4053.                 my $cmdval =
  4054.                 sprintf($sprintfcmd,
  4055.                     (defined($v->{'driverval'})
  4056.                      ? $v->{'driverval'}
  4057.                      : $v->{'value'}));
  4058.                 $foomaticstr = ripdirective($header, $cmdval) . 
  4059.                 "\n";
  4060.             }
  4061.             }
  4062.             # Make sure that the longname/translation exists
  4063.             if (!$v->{'comment'}) {
  4064.             if ($type !~ /^(string|password)$/) {
  4065.                 $v->{'comment'} = longname($v->{'value'});
  4066.             } else {
  4067.                 $v->{'comment'} = $v->{'value'};
  4068.             }
  4069.             }
  4070.             # Code supposed to be inserted into the PostScript
  4071.             # data when this choice is selected.
  4072.             push(@optionblob,
  4073.              sprintf("*%s %s/%s: \"%s\"\n", 
  4074.                  $name, $v->{'value'},
  4075.                  cutguiname($v->{'comment'}, $shortgui),
  4076.                  $psstr));
  4077.             # PostScript code is more than one line? Let an "*End"
  4078.             # line follow
  4079.             if ($psstr =~ /\n/s) {
  4080.             push(@optionblob, "*End\n");
  4081.             }
  4082.             # If we have a command line or JCL option, insert the
  4083.             # code here. For security reasons command line snippets
  4084.             # cannot be inserted into the "official" choice entry,
  4085.             # otherwise the appropriate RIP filter could execute
  4086.             # arbitrary code.
  4087.             push(@optionblob, $foomaticstr);
  4088.             # Stuff to insert into command line/job is more than one
  4089.             # line? Let an "*End" line follow
  4090.             if ($foomaticstr =~ /\n.*\n/s) {
  4091.             push(@optionblob, "*End\n");
  4092.             }
  4093.             # In modern PostScript interpreters "PageRegion" 
  4094.             # and "PageSize" are the same option, so we fill 
  4095.             # in the "PageRegion" the same
  4096.             # way as the "PageSize" choices.
  4097.             if ($name eq "PageSize") {
  4098.             push(@pageregion,
  4099.                  sprintf("*PageRegion %s/%s: \"%s\"", 
  4100.                      $v->{'value'}, $v->{'comment'},
  4101.                      $psstr));
  4102.             if ($psstr =~ /\n/s) {
  4103.                 push(@pageregion, "*End");
  4104.             }
  4105.             }
  4106.         }
  4107.         
  4108.         push(@optionblob,
  4109.              sprintf("*${jcl}CloseUI: *%s\n", $name));
  4110.  
  4111.                  # Insert Custom Option
  4112.         if ($type =~ /^(string|password)$/) {
  4113.             my $templ = $cmd;
  4114.             if ($optstyle eq "JCL") {
  4115.             $templ =~ s/%s/\\1/;
  4116.             if ($templ !~ m/^@/) {
  4117.                 $templ = "\@PJL " . $templ;
  4118.             }
  4119.             if ($templ !~ m/<0A>$/) {
  4120.                 $templ = $templ . "<0A>";
  4121.             }
  4122.             }
  4123.             elsif ($optstyle eq "CmdLine") {
  4124.             $templ = " pop ";
  4125.             }
  4126.             else {
  4127.             my $cnt = 0;
  4128.             my @words = split(/[ <>]/, $cmd);
  4129.             foreach my $word (@words) {
  4130.                 last if ($word eq '%s');
  4131.                 $cnt++ if ($word);
  4132.             }
  4133.             $templ =~ s/%s/ ${cnt} 1 roll /;
  4134.             }
  4135.             push(@optionblob, sprintf("*Custom%s%s True: \"%s\"\n", $jcl, $name, $templ));
  4136.             push(@optionblob,
  4137.             sprintf("*ParamCustom%s%s %s/%s: 1 %s 0 %d\n\n",
  4138.                 $jcl, $name, $name, $arg->{'comment'},
  4139.                 $type, $arg->{'maxlength'}));
  4140.         }
  4141.  
  4142.         if ($name eq "PageSize") {
  4143.             # Close the PageRegion, ImageableArea, and 
  4144.             # PaperDimension clauses
  4145.             push(@pageregion,
  4146.              "*${jcl}CloseUI: *PageRegion");
  4147.  
  4148.             my $paperdim = join("\n", 
  4149.                     ("", @pageregion, "", 
  4150.                      @imageablearea, "",
  4151.                      @paperdimension, ""));
  4152.             push (@optionblob, $paperdim);
  4153.  
  4154.             # Make the header entries for a custom page size
  4155.             if ($hascustompagesize) {
  4156.             my $maxpaperdim = 
  4157.                 ($maxpageheight > $maxpagewidth ?
  4158.                  $maxpageheight : $maxpagewidth);
  4159.             # PostScript code from the example 6 in section 6.3
  4160.             # of Adobe's PPD V4.3 specification
  4161.             # http://partners.adobe.com/asn/developer/pdfs/tn/5003.PPD_Spec_v4.3.pdf
  4162.             # If the page size is an option for the command line
  4163.             # of Ghostscript, let the values which where put
  4164.             # on the stack being popped and inserta comment
  4165.             # to advise the filter
  4166.             
  4167.             my $pscode;
  4168.             my $foomaticstr = "";
  4169.             if ($arg->{'style'} eq 'G') {
  4170.                 $pscode = "pop pop pop
  4171. <</PageSize [ 5 -2 roll ] /ImagingBBox null>>setpagedevice";
  4172.             } else {
  4173.                 my $a = $arg->{'vals_byname'}{'Custom'};
  4174.                 my $header = sprintf
  4175.                 ("*FoomaticRIPOptionSetting %s=%s",
  4176.                  $name, $a->{'value'});
  4177.                 my $sprintfcmd = $cmd;
  4178.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4179.                 my $cmdval =
  4180.                 sprintf($sprintfcmd,
  4181.                     (defined($a->{'driverval'})
  4182.                      ? $a->{'driverval'}
  4183.                      : $a->{'value'}));
  4184.                 $foomaticstr =
  4185.                 ripdirective($header, $cmdval) . "\n";
  4186.                 # Stuff to insert into command line/job is more
  4187.                 # than one line? Let an "*End" line follow
  4188.                 if ($foomaticstr =~ /\n.*\n/s) {
  4189.                 $foomaticstr .= "*End\n";
  4190.                 }
  4191.                 $pscode = "pop pop pop pop pop
  4192. %% FoomaticRIPOptionSetting: $name=Custom";
  4193.             }
  4194.             my ($left, $right, $top, $bottom) =
  4195.                 getmargins($dat, 0, 0, 'Custom');
  4196.             my $custompagesizeheader = 
  4197. "*HWMargins: $left $bottom $right $top
  4198. *VariablePaperSize: True
  4199. *MaxMediaWidth: $maxpaperdim
  4200. *MaxMediaHeight: $maxpaperdim
  4201. *NonUIOrderDependency: $order $section *CustomPageSize
  4202. *CustomPageSize True: \"$pscode\"
  4203. *End
  4204. ${foomaticstr}*ParamCustomPageSize Width: 1 points 36 $maxpagewidth
  4205. *ParamCustomPageSize Height: 2 points 36 $maxpageheight
  4206. *ParamCustomPageSize Orientation: 3 int 0 0
  4207. *ParamCustomPageSize WidthOffset: 4 points 0 0
  4208. *ParamCustomPageSize HeightOffset: 5 points 0 0
  4209.  
  4210. ";
  4211.             
  4212.             unshift (@optionblob, $custompagesizeheader);
  4213.             } else {
  4214.             unshift (@optionblob,
  4215.                  "*VariablePaperSize: False\n\n");
  4216.             }
  4217.         }
  4218.         } elsif (((1 == scalar(@{$arg->{'vals'}})) &&
  4219.               ($arg->{'style'} ne 'G')) ||
  4220.              ($arg->{'hidden'})) {
  4221.         # non-PostScript enumerated choice option with one single 
  4222.         # choice or hidden member option of forced composite
  4223.         # option
  4224.  
  4225.         # Insert line with option properties
  4226.         my $foomaticstrs = '';
  4227.         for my $v (@{$arg->{'vals'}}) {
  4228.             my $header = sprintf
  4229.             ("*FoomaticRIPOptionSetting %s=%s",
  4230.              $name, $v->{'value'});
  4231.             my $cmdval = '';
  4232.             # For the "From<Composite>" setting the command line
  4233.             # value is not made use of, so leave it blank then.
  4234.             if ($v->{'driverval'} ne "\x01") {
  4235.             my $sprintfcmd = $cmd;
  4236.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4237.             $cmdval =
  4238.                 sprintf($sprintfcmd,
  4239.                     (defined($v->{'driverval'})
  4240.                      ? $v->{'driverval'}
  4241.                      : $v->{'value'}));
  4242.             }
  4243.             my $foomaticstr = ripdirective($header, $cmdval) . "\n";
  4244.             # Stuff to insert into command line/job is more
  4245.             # than one line? Let an "*End" line follow
  4246.             if ($foomaticstr =~ /\n.*\n/s) {
  4247.             $foomaticstr .= "*End\n";
  4248.             }
  4249.             $foomaticstrs .= $foomaticstr;
  4250.         }
  4251.         push(@optionblob, sprintf
  4252.              ("\n*FoomaticRIPOption %s: %s %s %s %s\n",
  4253.               $name, $type, $optstyle, $spot, $order),
  4254.              $stringextralines1, $foomaticstrs);
  4255.         }
  4256.     } elsif ($type eq 'bool') {
  4257.         my $name = $arg->{'name'};
  4258.         my $namef = $arg->{'name_false'};
  4259.         my $defstr = ($default ? 'True' : 'False');
  4260.         if (!defined($default)) { 
  4261.         $defstr = 'Unknown';
  4262.         }
  4263.         my $psstr = "";
  4264.         my $psstrf = "";
  4265.  
  4266.         push(@optionblob,
  4267.          sprintf("\n*${jcl}OpenUI *%s/%s: Boolean\n", $name, 
  4268.              cutguiname($com, $shortgui)));
  4269.  
  4270.         if ($arg->{'style'} eq 'G' || $optstyle eq "JCL") {
  4271.         # Ghostscript argument
  4272.         $psstr = $cmd;
  4273.         # Boolean options should not use the "%s" default for $cmd
  4274.         $psstr =~ s/^%s$//;
  4275.  
  4276.         if ($optstyle eq "JCL") {
  4277.             if ($psstr !~ m/^@/) {
  4278.             $psstr = "\@PJL " . $psstr;
  4279.             }
  4280.             if ($psstr !~ m/<0A>$/) {
  4281.             $psstr = $psstr . "<0A>";
  4282.             }
  4283.         }
  4284.         } else {
  4285.         # Option setting directive for Foomatic filter
  4286.         # 4 "%" because of the "sprintf" applied to it
  4287.         # In the end stay 2 "%" to have a PostScript comment
  4288.         my $header = sprintf
  4289.             ("%%%% FoomaticRIPOptionSetting: %s", $name);
  4290.         $psstr = "$header=True";
  4291.         $psstrf = "$header=False";
  4292.         $header = sprintf
  4293.             ("*FoomaticRIPOptionSetting %s", $name);
  4294.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  4295.         # For non-PostScript options insert line with option
  4296.         # properties
  4297.         push(@optionblob, sprintf
  4298.              ("*FoomaticRIPOption %s: bool %s %s\n",
  4299.               $name, $optstyle, $spot).
  4300.              $foomaticstr,
  4301.              ($foomaticstr =~ /\n.*\n/s ? "*End\n" : ""));
  4302.         }
  4303.  
  4304.         push(@optionblob,
  4305.          sprintf("*OrderDependency: %s %s *%s\n", 
  4306.              $order, $section, $name),
  4307.          sprintf("*Default%s: $defstr\n", $name),
  4308.          sprintf("*%s True/%s: \"%s\"\n", $name, 
  4309.              cutguiname($name, $shortgui), $psstr),
  4310.          ($psstr =~ /\n/s ? "*End\n" : ""),
  4311.          sprintf("*%s False/%s: \"%s\"\n", $name,
  4312.              cutguiname($namef, $shortgui), $psstrf),
  4313.          ($psstrf =~ /\n/s ? "*End\n" : ""),
  4314.          sprintf("*${jcl}CloseUI: *%s\n", $name));
  4315.         
  4316.     } elsif ($type eq 'int') {
  4317.  
  4318.         # Real numerical options do not exist in the Adobe
  4319.         # specification for PPD files. So we map the numerical
  4320.         # options to enumerated options offering the minimum, the
  4321.         # maximum, the default, and some values inbetween to the
  4322.         # user.
  4323.  
  4324.         my $min = $arg->{'min'};
  4325.         my $max = $arg->{'max'};
  4326.         my $second = $min + 1;
  4327.         my $stepsize = 1;
  4328.         if (($max - $min > 100) && ($name ne "Copies")) {
  4329.         # We don't want to have more than 100 values, but when the
  4330.         # difference between min and max is more than 100 we should
  4331.         # have at least 10 steps.
  4332.         my $mindesiredvalues = 10;
  4333.         my $maxdesiredvalues = 100;
  4334.         # Find the order of magnitude of the value range
  4335.         my $rangesize = $max - $min;
  4336.         my $log10 = log(10.0);
  4337.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  4338.         # Now find the step size
  4339.         my $trialstepsize = 10 ** $rangeom;
  4340.         my $numvalues = 0;
  4341.         while (($numvalues <= $mindesiredvalues) &&
  4342.                ($trialstepsize > 2)) {
  4343.             $trialstepsize /= 10;
  4344.             $numvalues = $rangesize/$trialstepsize;
  4345.         }
  4346.         # Try to find a finer stepping
  4347.         $stepsize = $trialstepsize;
  4348.         $trialstepsize = $stepsize / 2;
  4349.         $numvalues = $rangesize/$trialstepsize;
  4350.         if ($numvalues <= $maxdesiredvalues) {
  4351.             if ($stepsize > 20) { 
  4352.             $trialstepsize = $stepsize / 4;
  4353.             $numvalues = $rangesize/$trialstepsize;
  4354.             }
  4355.             if ($numvalues <= $maxdesiredvalues) {
  4356.             $trialstepsize = $stepsize / 5;
  4357.             $numvalues = $rangesize/$trialstepsize;
  4358.             }
  4359.             if ($numvalues <= $maxdesiredvalues) {
  4360.             $stepsize = $trialstepsize;
  4361.             } else {
  4362.             $stepsize /= 2;
  4363.             }
  4364.         }
  4365.         $numvalues = $rangesize/$stepsize;
  4366.         # We have the step size. Now we must find an appropriate
  4367.         # second value for the value list, so that it contains
  4368.         # the integer multiples of 10, 100, 1000, ...
  4369.         $second = $stepsize * POSIX::ceil($min / $stepsize);
  4370.         if ($second <= $min) {$second += $stepsize};
  4371.         }
  4372.         # Generate the choice list
  4373.         my @choicelist;
  4374.         push (@choicelist, $min);
  4375.         if (($default < $second) && ($default > $min)) {
  4376.         push (@choicelist, $default);
  4377.         }
  4378.         my $item = $second;
  4379.         while ($item < $max) {
  4380.         push (@choicelist, $item);
  4381.         if (($default < $item + $stepsize) && ($default > $item) &&
  4382.             ($default < $max)) {
  4383.             push (@choicelist, $default);
  4384.         }
  4385.         $item += $stepsize;
  4386.         }
  4387.         push (@choicelist, $max);
  4388.  
  4389.             # Add the option
  4390.  
  4391.         # Skip zero or one choice arguments
  4392.         if (1 < scalar(@choicelist)) {
  4393.         push(@optionblob,
  4394.              sprintf("\n*${jcl}OpenUI *%s/%s: PickOne\n", $name,
  4395.                  cutguiname($com, $shortgui)));
  4396.  
  4397.         # Insert lines with the special properties of a
  4398.         # numerical option. Do this also for PostScript options
  4399.         # because numerical options are not supported by the PPD
  4400.         # file syntax. This way the info about this option being
  4401.         # a numerical one does not get lost
  4402.  
  4403.         push(@optionblob, sprintf
  4404.              ("*FoomaticRIPOption %s: int %s %s\n",
  4405.               $name, $optstyle, $spot));
  4406.  
  4407.         my $header = sprintf
  4408.             ("*FoomaticRIPOptionPrototype %s",
  4409.              $name);
  4410.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  4411.         push(@optionblob, $foomaticstr);
  4412.         # Stuff to insert into command line/job is more than one
  4413.         # line? Let an "*End" line follow
  4414.         if ($foomaticstr =~ /\n.*\n/s) {
  4415.             push(@optionblob, "*End\n");
  4416.         }
  4417.  
  4418.         push(@optionblob, sprintf
  4419.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  4420.               $name, $arg->{'min'}, $arg->{'max'}));
  4421.  
  4422.         push(@optionblob,
  4423.              sprintf("*OrderDependency: %s %s *%s\n", 
  4424.                  $order, $section, $name),
  4425.              sprintf("*Default%s: %s\n", 
  4426.                  $name,
  4427.                  (defined($default) ? $default : 'Unknown')),
  4428.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  4429.                  $name,
  4430.                  (defined($default) ? $default : 'Unknown')));
  4431.         if (!defined($default)) {
  4432.             my $whr = sprintf("%s %s driver %s",
  4433.                       $dat->{'make'},
  4434.                       $dat->{'model'},
  4435.                       $dat->{'driver'});
  4436.             warn "undefined default for $idx/$name on a $whr\n";
  4437.         }
  4438.         
  4439.         for my $v (@choicelist) {
  4440.             my $psstr = "";
  4441.             
  4442.             if ($optstyle eq "PS"|| $optstyle eq "JCL") {
  4443.             # Ghostscript argument; offer up ps for insertion
  4444.             my $sprintfcmd = $cmd;
  4445.             if ($optstyle eq "JCL") {
  4446.                 if ($sprintfcmd !~ m/^@/) {
  4447.                 $sprintfcmd = "\@PJL " . $sprintfcmd;
  4448.                 }
  4449.                 if ($sprintfcmd !~ m/<0A>$/) {
  4450.                 $sprintfcmd = $sprintfcmd . "<0A>";
  4451.                 }
  4452.             }
  4453.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4454.             $psstr = sprintf($sprintfcmd, $v);
  4455.             } else {
  4456.             # Option setting directive for Foomatic filter
  4457.             # 4 "%" because of the "sprintf" applied to it
  4458.             # In the end stay 2 "%" to have a PostScript comment
  4459.             $psstr = sprintf
  4460.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  4461.                   $name, $v);
  4462.             }
  4463.             push(@optionblob,
  4464.              sprintf("*%s %s/%s: \"%s\"\n", 
  4465.                  $name, $v, 
  4466.                  cutguiname($v, $shortgui), $psstr));
  4467.             # PostScript code is more than one line? Let an "*End"
  4468.             # line follow
  4469.             if ($psstr =~ /\n/s) {
  4470.             push(@optionblob, "*End\n");
  4471.             }
  4472.         }
  4473.         
  4474.         push(@optionblob,
  4475.             sprintf("*${jcl}CloseUI: *%s\n\n", $name));
  4476.  
  4477.         # Insert custom option
  4478.         my $templ = $cmd;
  4479.         if ($optstyle eq "JCL") {
  4480.             $templ =~ s/%s/\\1/;
  4481.             if ($templ !~ m/^@/) {
  4482.             $templ = "\@PJL " . $templ;
  4483.             }
  4484.             if ($templ !~ m/<0A>$/) {
  4485.             $templ = $templ . "<0A>";
  4486.             }
  4487.         }
  4488.         elsif ($optstyle eq "CmdLine") {
  4489.             $templ = " pop ";
  4490.         }
  4491.         else {
  4492.             my $cnt = 0;
  4493.             my @words = split(/[ <>]/, $cmd);
  4494.             foreach my $word (@words) {
  4495.             last if ($word eq '%s');
  4496.             $cnt++ if ($word);
  4497.             }
  4498.             $templ =~ s/%s/ ${cnt} 1 roll /;
  4499.         }
  4500.         push(@optionblob, sprintf("*Custom%s%s True: \"%s\"\n", $jcl, $name, $templ));
  4501.         push(@optionblob,
  4502.             sprintf("*ParamCustom%s%s %s/%s: 1 int %d %d\n\n",
  4503.             $jcl, $name, $name, $arg->{'comment'}, $min, $max));
  4504.         }
  4505.     } elsif ($type eq 'float') {
  4506.  
  4507.         # Real numerical options do not exist in the Adobe
  4508.         # specification for PPD files. So we map the numerical
  4509.         # options to enumerated options offering the minimum, the
  4510.         # maximum, the default, and some values inbetween to the
  4511.         # user.
  4512.  
  4513.         my $min = $arg->{'min'};
  4514.         my $max = $arg->{'max'};
  4515.         # We don't want to have more than 500 values or less than 50
  4516.         # values.
  4517.         my $mindesiredvalues = 10;
  4518.         my $maxdesiredvalues = 100;
  4519.         # Find the order of magnitude of the value range
  4520.         my $rangesize = $max - $min;
  4521.         my $log10 = log(10.0);
  4522.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  4523.         # Now find the step size
  4524.         my $trialstepsize = 10 ** $rangeom;
  4525.         my $stepom = $rangeom; # Order of magnitude of stepsize,
  4526.                                # needed for determining necessary number
  4527.                                # of digits
  4528.         my $numvalues = 0;
  4529.         while ($numvalues <= $mindesiredvalues) {
  4530.         $trialstepsize /= 10;
  4531.         $stepom -= 1;
  4532.         $numvalues = $rangesize/$trialstepsize;
  4533.         }
  4534.         # Try to find a finer stepping
  4535.         my $stepsize = $trialstepsize;
  4536.         my $stepsizeorig = $stepsize;
  4537.         $trialstepsize = $stepsizeorig / 2;
  4538.         $numvalues = $rangesize/$trialstepsize;
  4539.         if ($numvalues <= $maxdesiredvalues) {
  4540.         $stepsize = $trialstepsize;
  4541.         $trialstepsize = $stepsizeorig / 4;
  4542.         $numvalues = $rangesize/$trialstepsize;
  4543.         if ($numvalues <= $maxdesiredvalues) {
  4544.             $stepsize = $trialstepsize;
  4545.             $trialstepsize = $stepsizeorig / 5;
  4546.             $numvalues = $rangesize/$trialstepsize;
  4547.             if ($numvalues <= $maxdesiredvalues) {
  4548.             $stepsize = $trialstepsize;
  4549.             }
  4550.         }
  4551.         }
  4552.         $numvalues = $rangesize/$stepsize;
  4553.         if ($stepsize < $stepsizeorig * 0.9) {$stepom -= 1;}
  4554.         # Determine number of digits after the decimal point for
  4555.         # formatting the output values.
  4556.         my $digits = 0;
  4557.         if ($stepom < 0) {
  4558.         $digits = - $stepom;
  4559.         }
  4560.         # We have the step size. Now we must find an appropriate
  4561.         # second value for the value list, so that it contains
  4562.         # the integer multiples of 10, 100, 1000, ...
  4563.         my $second = $stepsize * POSIX::ceil($min / $stepsize);
  4564.         if ($second <= $min) {$second += $stepsize};
  4565.         # Generate the choice list
  4566.         my @choicelist;
  4567.         my $choicestr =  sprintf("%.${digits}f", $min);
  4568.         push (@choicelist, $choicestr);
  4569.         if (($default < $second) && ($default > $min)) {
  4570.         $choicestr =  sprintf("%.${digits}f", $default);
  4571.         # Prevent values from entering twice because of rounding
  4572.         # inacuracy
  4573.         if ($choicestr ne $choicelist[$#choicelist]) {
  4574.             push (@choicelist, $choicestr);
  4575.         }
  4576.         }
  4577.         my $item = $second;
  4578.         my $i = 0;
  4579.         while ($item < $max) {
  4580.         $choicestr =  sprintf("%.${digits}f", $item);
  4581.         # Prevent values from entering twice because of rounding
  4582.         # inacuracy
  4583.         if ($choicestr ne $choicelist[$#choicelist]) {
  4584.             push (@choicelist, $choicestr);
  4585.         }
  4586.         if (($default < $item + $stepsize) && ($default > $item) &&
  4587.             ($default < $max)) {
  4588.             $choicestr =  sprintf("%.${digits}f", $default);
  4589.             # Prevent values from entering twice because of rounding
  4590.             # inacuracy
  4591.             if ($choicestr ne $choicelist[$#choicelist]) {
  4592.             push (@choicelist, $choicestr);
  4593.             }
  4594.         }
  4595.         $i += 1;
  4596.         $item = $second + $i * $stepsize;
  4597.         }
  4598.         $choicestr =  sprintf("%.${digits}f", $max);
  4599.         # Prevent values from entering twice because of rounding
  4600.         # inacuracy
  4601.         if ($choicestr ne $choicelist[$#choicelist]) {
  4602.         push (@choicelist, $choicestr);
  4603.         }
  4604.  
  4605.             # Add the option
  4606.  
  4607.         # Skip zero or one choice arguments
  4608.         if (1 < scalar(@choicelist)) {
  4609.         push(@optionblob,
  4610.              sprintf("\n*${jcl}OpenUI *%s/%s: PickOne\n", $name, 
  4611.                  cutguiname($com, $shortgui)));
  4612.  
  4613.         # Insert lines with the special properties of a
  4614.         # numerical option. Do this also for PostScript options
  4615.         # because numerical options are not supported by the PPD
  4616.         # file syntax. This way the info about this option being
  4617.         # a numerical one does not get lost
  4618.  
  4619.         push(@optionblob, sprintf
  4620.              ("*FoomaticRIPOption %s: float %s %s\n",
  4621.               $name, $optstyle, $spot));
  4622.  
  4623.         my $header = sprintf
  4624.             ("*FoomaticRIPOptionPrototype %s",
  4625.              $name);
  4626.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  4627.         push(@optionblob, $foomaticstr);
  4628.         # Stuff to insert into command line/job is more than one
  4629.         # line? Let an "*End" line follow
  4630.         if ($foomaticstr =~ /\n.*\n/s) {
  4631.             push(@optionblob, "*End\n");
  4632.         }
  4633.  
  4634.         push(@optionblob, sprintf
  4635.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  4636.               $name, $arg->{'min'}, $arg->{'max'}));
  4637.  
  4638.         push(@optionblob,
  4639.              sprintf("*OrderDependency: %s %s *%s\n", 
  4640.                  $order, $section, $name),
  4641.              sprintf("*Default%s: %s\n", 
  4642.                  $name,
  4643.                  (defined($default) ? 
  4644.                   sprintf("%.${digits}f", $default) : 'Unknown')),
  4645.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  4646.                  $name,
  4647.                  (defined($default) ? 
  4648.                   sprintf("%.${digits}f", $default) : 'Unknown')));
  4649.         if (!defined($default)) {
  4650.             my $whr = sprintf("%s %s driver %s",
  4651.                       $dat->{'make'},
  4652.                       $dat->{'model'},
  4653.                       $dat->{'driver'});
  4654.             warn "undefined default for $idx/$name on a $whr\n";
  4655.         }
  4656.  
  4657.         for my $v (@choicelist) {
  4658.             my $psstr = "";
  4659.             if ($arg->{'style'} eq 'G') {
  4660.             # Ghostscript argument; offer up ps for insertion
  4661.             my $sprintfcmd = $cmd;
  4662.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4663.             $psstr = sprintf($sprintfcmd, $v);
  4664.             } else {
  4665.             # Option setting directive for Foomatic filter
  4666.             # 4 "%" because of the "sprintf" applied to it
  4667.             # In the end stay 2 "%" to have a PostScript comment
  4668.             $psstr = sprintf
  4669.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  4670.                   $name, $v);
  4671.             }
  4672.             push(@optionblob,
  4673.              sprintf("*%s %s/%s: \"%s\"\n", 
  4674.                  $name, $v, 
  4675.                  cutguiname($v, $shortgui), $psstr));
  4676.             # PostScript code is more than one line? Let an "*End"
  4677.             # line follow
  4678.             if ($psstr =~ /\n/s) {
  4679.             push(@optionblob, "*End\n");
  4680.             }
  4681.         }
  4682.         
  4683.         push(@optionblob,
  4684.              sprintf("*${jcl}CloseUI: *%s\n\n", $name));
  4685.  
  4686.         # Insert custom option
  4687.         my $templ = $cmd;
  4688.         if ($optstyle eq "JCL") {
  4689.             $templ =~ s/%s/\\1/;
  4690.             if ($templ !~ m/^@/) {
  4691.             $templ = "\@PJL " . $templ;
  4692.             }
  4693.             if ($templ !~ m/<0A>$/) {
  4694.             $templ = $templ . "<0A>";
  4695.             }
  4696.         }
  4697.         elsif ($optstyle eq "CmdLine") {
  4698.             $templ = " pop ";
  4699.         }
  4700.         else {
  4701.             my $cnt = 0;
  4702.             my @words = split(/[ <>]/, $cmd);
  4703.             foreach my $word (@words) {
  4704.             last if ($word eq '%s');
  4705.             $cnt++ if ($word);
  4706.             }
  4707.             $templ =~ s/%s/ ${cnt} 1 roll /;
  4708.         }
  4709.         push(@optionblob, sprintf("*Custom%s%s True: \"%s\"\n", $jcl, $name, $templ));
  4710.         push(@optionblob,
  4711.             sprintf("*ParamCustom%s%s %s/%s: 1 real %f %f\n\n",
  4712.             $jcl, $name, $name, $arg->{'comment'}, $min, $max));
  4713.  
  4714.         }
  4715.         }
  4716.     }
  4717.  
  4718.     # Close the option groups which are still open
  4719.     for (my $i = $#groupstack; $i >= 0; $i--) {
  4720.     push(@optionblob,
  4721.          sprintf("\n*Close%sGroup: %s\n",
  4722.              ($i > 0 ? "Sub" : ""), $groupstack[$i])
  4723.          );
  4724.     pop(@groupstack);
  4725.     }
  4726.  
  4727.     if (! $dat->{'args_byname'}{'PageSize'} ) {
  4728.     
  4729.     # This is a problem, since CUPS segfaults on PPD files without
  4730.     # a default PageSize set.  Indeed, the PPD spec requires a
  4731.     # PageSize clause.
  4732.     
  4733.     # Ghostscript does not understand "/PageRegion[...]", therefore
  4734.     # we use "/PageSize[...]" in the "*PageRegion" option here, in
  4735.     # addition, for most modern PostScript interpreters "PageRegion"
  4736.     # is the same as "PageSize".
  4737.  
  4738.     push(@optionblob, <<EOFPGSZ);
  4739.  
  4740. *% This is fake. We have no information on how to
  4741. *% set the pagesize for this driver in the database. To
  4742. *% prevent PPD users from blowing up, we must provide a
  4743. *% default pagesize value.
  4744.  
  4745. *OpenUI *PageSize/Media Size: PickOne
  4746. *OrderDependency: 10 AnySetup *PageSize
  4747. *DefaultPageSize: Letter
  4748. *PageSize Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  4749. *PageSize Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  4750. *PageSize A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  4751. *CloseUI: *PageSize
  4752.  
  4753. *OpenUI *PageRegion: PickOne
  4754. *OrderDependency: 10 AnySetup *PageRegion
  4755. *DefaultPageRegion: Letter
  4756. *PageRegion Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  4757. *PageRegion Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  4758. *PageRegion A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  4759. *CloseUI: *PageRegion
  4760.  
  4761. *DefaultImageableArea: Letter
  4762. *ImageableArea Letter/Letter:    "0 0 612 792"
  4763. *ImageableArea Legal/Legal:    "0 0 612 1008"
  4764. *ImageableArea A4/A4:    "0 0 595 842"
  4765.  
  4766. *DefaultPaperDimension: Letter
  4767. *PaperDimension Letter/Letter:    "612 792"
  4768. *PaperDimension Legal/Legal:    "612 1008"
  4769. *PaperDimension A4/A4:    "595 842"
  4770.  
  4771. EOFPGSZ
  4772.     }
  4773.  
  4774.     my @others;
  4775.  
  4776.     my $headcomment =
  4777. "*% For information on using this, and to obtain the required backend
  4778. *% script, consult http://www.openprinting.org/
  4779. *%
  4780. *% This file is published under the GNU General Public License
  4781. *%
  4782. *% PPD-O-MATIC (4.0.0 or newer) generated this PPD file. It is for use with 
  4783. *% all programs and environments which use PPD files for dealing with
  4784. *% printer capability information. The printer must be configured with the
  4785. *% \"foomatic-rip\" backend filter script of Foomatic 4.0.0 or newer. This 
  4786. *% file and \"foomatic-rip\" work together to support PPD-controlled printer
  4787. *% driver option access with all supported printer drivers and printing
  4788. *% spoolers.
  4789. *%
  4790. *% To save this file on your disk, wait until the download has completed
  4791. *% (the animation of the browser logo must stop) and then use the
  4792. *% \"Save as...\" command in the \"File\" menu of your browser or in the 
  4793. *% pop-up manu when you click on this document with the right mouse button.
  4794. *% DO NOT cut and paste this file into an editor with your mouse. This can
  4795. *% introduce additional line breaks which lead to unexpected results.";
  4796.  
  4797.     my $postpipe = "";
  4798.     if ($dat->{'postpipe'}) {
  4799.     my $header = "*FoomaticRIPPostPipe";
  4800.     my $code = $dat->{'postpipe'};
  4801.     $postpipe = ripdirective($header, $code) . "\n";
  4802.     if ($postpipe =~ /\n.*\n/s) {
  4803.         $postpipe .= "*End\n";
  4804.     }
  4805.     }
  4806.     my $opts = join('',@optionblob);
  4807.     my $otherstuff = join('',@others);
  4808.     my $pcfilename;
  4809.     if (($dat->{'pcmodel'}) && ($dat->{'pcdriver'})) {
  4810.     $pcfilename = uc("$dat->{'pcmodel'}$dat->{'pcdriver'}");
  4811.     } else {
  4812.     my $driver = $dat->{'driver'};
  4813.     $driver =~ m!(^(.{1,8}))!;
  4814.     $pcfilename = uc($1);
  4815.     }
  4816.     $pcfilename = 'FOOMATIC' if !defined($pcfilename);
  4817.     my $model = $dat->{'model'};
  4818.     my $make = $dat->{'make'};
  4819.     my ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  4820.     $drivername,$nickname,$modelname) =
  4821.         getppdheaderdata($dat, $dat->{'driver'}, $dat->{'recdriver'});
  4822.     if ($ieee1284) {
  4823.     $ieee1284 = "*1284DeviceID: \"" . $ieee1284 . "\"";
  4824.     }
  4825.  
  4826.     # Add info about driver properties
  4827.     my $drvproperties = "";
  4828.     $drvproperties .= "*driverName $dat->{'driver'}: \"" .
  4829.     ($dat->{'shortdescription'} ? 
  4830.      $dat->{'shortdescription'} : "") . 
  4831.      "\"\n" if defined($dat->{'driver'});
  4832.     $drvproperties .= "*driverType $dat->{'type'}" .
  4833.     ($dat->{'type'} eq "G" ? "/Ghostscript built-in" :
  4834.      ($dat->{'type'} eq "U" ? "/Ghostscript Uniprint" :
  4835.       ($dat->{'type'} eq "F" ? "/Filter" :
  4836.        ($dat->{'type'} eq "C" ? "/CUPS Raster" :
  4837.         ($dat->{'type'} eq "V" ? "/OpenPrinting Vector" :
  4838.          ($dat->{'type'} eq "I" ? "/IJS" :
  4839.           ($dat->{'type'} eq "P" ? "/PostScript" : ""))))))) . 
  4840.           ": \"\"\n" if defined($dat->{'type'});
  4841.     $drvproperties .= "*driverUrl: \"$dat->{'url'}\"\n" if
  4842.     defined($dat->{'url'});
  4843.     if ((defined($dat->{'obsolete'})) &&
  4844.     ($dat->{'obsolete'} ne "0")) {
  4845.     $drvproperties .= "*driverObsolete: True\n";
  4846.     if ($dat->{'obsolete'} ne "1") {
  4847.         $drvproperties .= "*driverRecommendedReplacement: " .
  4848.         "\"$dat->{'obsolete'}\"\n";
  4849.     }
  4850.     } else {
  4851.     $drvproperties .= "*driverObsolete: False\n";
  4852.     }
  4853.     $drvproperties .= "*driverSupplier: \"$dat->{'supplier'}\"\n" if
  4854.     defined($dat->{'supplier'});
  4855.     $drvproperties .= "*driverManufacturerSupplied: " . 
  4856.     ($dat->{'manufacturersupplied'} eq "1" ? "True" : 
  4857.      ($dat->{make} =~ m,^($dat->{'manufacturersupplied'})$,i ? "True" :
  4858.       "False")) . "\n" if
  4859.     defined($dat->{'manufacturersupplied'});
  4860.     $drvproperties .= "*driverLicense: \"$dat->{'license'}\"\n" if
  4861.     defined($dat->{'license'});
  4862.     $drvproperties .= "*driverFreeSoftware: " . 
  4863.     ($dat->{'free'} ? "True" : "False") . "\n" if
  4864.     defined($dat->{'free'});
  4865.     if (defined($dat->{'supportcontacts'})) {
  4866.     foreach my $entry (@{$dat->{'supportcontacts'}}) {
  4867.         my $uclevel = uc(substr($entry->{'level'}, 0, 1)) .
  4868.         lc(substr($entry->{'level'}, 1));
  4869.         $drvproperties .= "*driverSupportContact${uclevel}: " .
  4870.         "\"$entry->{'url'} $entry->{'description'}\"\n";
  4871.     }
  4872.     }
  4873.     if (defined($dat->{'drvmaxresx'}) || defined($dat->{'drvmaxresy'})) {
  4874.     my ($maxresx, $maxresy);
  4875.     $maxresx = $dat->{'drvmaxresx'} if defined($dat->{'drvmaxresx'});
  4876.     $maxresy = $dat->{'drvmaxresy'} if defined($dat->{'drvmaxresy'});
  4877.     $maxresx = $maxresy if !$maxresx;
  4878.     $maxresy = $maxresx if !$maxresy;
  4879.     $drvproperties .= "*driverMaxResolution: " .
  4880.         "${maxresx} ${maxresy}\n";
  4881.     }
  4882.     $drvproperties .= "*driverColor: " . 
  4883.     ($dat->{'drvcolor'} ? "True" : "False") . "\n" if
  4884.     defined($dat->{'drvcolor'});
  4885.     $drvproperties .= "*driverTextSupport: $dat->{'text'}\n" if
  4886.     defined($dat->{'text'});
  4887.     $drvproperties .= "*driverLineartSupport: $dat->{'lineart'}\n" if
  4888.     defined($dat->{'lineart'});
  4889.     $drvproperties .= "*driverGraphicsSupport: $dat->{'graphics'}\n" if
  4890.     defined($dat->{'graphics'});
  4891.     $drvproperties .= "*driverPhotoSupport: $dat->{'photo'}\n" if
  4892.     defined($dat->{'photo'});
  4893.     $drvproperties .= "*driverSystemmLoad: $dat->{'load'}\n" if
  4894.     defined($dat->{'load'});
  4895.     $drvproperties .= "*driverRenderingSpeed: $dat->{'speed'}\n" if
  4896.     defined($dat->{'speed'});
  4897.     $drvproperties = "\n$drvproperties" if $drvproperties;
  4898.  
  4899.     # Do not use "," or "+" in the *ShortNickName to make the Windows
  4900.     # PostScript drivers happy
  4901.     my $shortnickname = "$make $model $drivername";
  4902.     if (length($shortnickname) > 31) {
  4903.     # ShortNickName too long? Shorten it.
  4904.     my %parts;
  4905.     $parts{'make'} = $make;
  4906.     $parts{'model'} = $model;
  4907.     $parts{'driver'} = $drivername;
  4908.     # Go through the three components, begin with model name, then
  4909.     # make and then driver
  4910.     for my $part (qw/model make driver/) {
  4911.         # Split the component into words, cutting always at the right edge
  4912.         # of the word. Cut also at a capital in the middle of the word
  4913.         # (ex: "S" in "PostScript").
  4914.         my @words = split(/(?<=[a-zA-Z])(?![a-zA-Z])|(?<=[a-z])(?=[A-Z])/,
  4915.                   $parts{$part});
  4916.         # Go through all words
  4917.         for (@words) {
  4918.         # Do not abbreviate words of less than 4 letters
  4919.         next if ($_ !~ /[a-zA-Z]{4,}$/);
  4920.         # How many letters did we chop off
  4921.         my $abbreviated = 0;
  4922.             while (1) {
  4923.             # Remove the last letter
  4924.             chop;
  4925.             $abbreviated ++;
  4926.             # Build the shortened component ...
  4927.             $parts{$part} = join('', @words);
  4928.             # ... and the ShortNickName
  4929.             $shortnickname =
  4930.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  4931.             # Stop if the ShostNickName has 30 characters or less
  4932.             # (we have still to add the abbreviation point), if there
  4933.             # is only one letter left, or if the manufacturer name
  4934.             # is reduced to three characters. Do not accept an
  4935.             # abbreviation of one character, as, taking the
  4936.             # abbreviation point into account, it does not save
  4937.             # a character.
  4938.             last if (((length($shortnickname) <= 30) &&
  4939.                   ($abbreviated != 1)) ||
  4940.                  ($_ !~ /[a-zA-Z]{2,}$/) ||
  4941.                  ((length($parts{'make'}) <= 3) &&
  4942.                   ($abbreviated != 1)));
  4943.         }
  4944.         #Abbreviation point
  4945.         if ($abbreviated) {
  4946.             $_ .= '.';
  4947.         }
  4948.         $parts{$part} = join('', @words);
  4949.         $shortnickname =
  4950.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  4951.         last if (length($shortnickname) <= 31);
  4952.         }
  4953.         last if (length($shortnickname) <= 31);
  4954.     }
  4955.     while ((length($shortnickname) > 31) &&
  4956.            (length($parts{'model'}) > 3)) {
  4957.         # ShortNickName too long? Remove last words from model name.
  4958.         $parts{'model'} =~
  4959.         s/(?<=[a-zA-Z0-9])[^a-zA-Z0-9]+[a-zA-Z0-9]*$//;
  4960.         my $new =
  4961.         "$parts{'make'} $parts{'model'}, $parts{'driver'}";
  4962.         last if ($new eq $shortnickname);
  4963.         $shortnickname = $new;
  4964.     }
  4965.     if (length($shortnickname) > 31) {
  4966.         # If nothing else helps ...
  4967.         $shortnickname = substr($shortnickname, 0, 31);
  4968.     }
  4969.     }
  4970.  
  4971.     my $color;
  4972.     if ($dat->{'color'}) {
  4973.     $color = "*ColorDevice:    True\n*DefaultColorSpace: RGB";
  4974.     } else {
  4975.     $color = "*ColorDevice:    False\n*DefaultColorSpace: Gray";
  4976.     }
  4977.  
  4978.     # Clean up "<ppdentry>"s
  4979.     foreach my $type ('printerppdentry', 'driverppdentry', 'comboppdentry'){
  4980.     if (defined($dat->{$type})) {
  4981.         $dat->{$type} =~ s/^\s+//gm;
  4982.         $dat->{$type} =~ s/\s+$//gm;
  4983.         $dat->{$type} =~ s/^\n+//gs;
  4984.         $dat->{$type} =~ s/\n*$/\n/gs;
  4985.     } else {
  4986.         $dat->{$type} = '';
  4987.     }
  4988.     }
  4989.     my $extralines = ($dat->{'comboppdentry'} ?
  4990.               $dat->{'comboppdentry'} :
  4991.               $dat->{'printerppdentry'} .
  4992.               $dat->{'driverppdentry'});
  4993.  
  4994.     my $tmpl = get_tmpl();
  4995.     $tmpl =~ s!\@\@POSTPIPE\@\@!$postpipe!g;
  4996.     $tmpl =~ s!\@\@HEADCOMMENT\@\@!$headcomment!g;
  4997.     $tmpl =~ s!\@\@SAVETHISAS\@\@!$longname!g;
  4998.     $tmpl =~ s!\@\@PCFILENAME\@\@!$pcfilename!g;
  4999.     $tmpl =~ s!\@\@MANUFACTURER\@\@!$make!g;
  5000.     $tmpl =~ s!\@\@PNPMAKE\@\@!$pnpmake!g;
  5001.     $tmpl =~ s!\@\@PNPMODEL\@\@!$pnpmodel!g;
  5002.     $tmpl =~ s!\@\@MODEL\@\@!$modelname!g;
  5003.     $tmpl =~ s!\@\@NICKNAME\@\@!$nickname!g;
  5004.     $tmpl =~ s!\@\@SHORTNICKNAME\@\@!$shortnickname!g;
  5005.     $tmpl =~ s!\@\@COLOR\@\@!$color!g;
  5006.     $tmpl =~ s!\@\@IEEE1284\@\@!$ieee1284!g;
  5007.     $tmpl =~ s!\@\@DRIVERPROPERTIES\@\@!$drvproperties!g;
  5008.     $tmpl =~ s!\@\@OTHERSTUFF\@\@!$otherstuff!g;
  5009.     $tmpl =~ s!\@\@OPTIONS\@\@!$opts!g;
  5010.     $tmpl =~ s!\@\@EXTRALINES\@\@!$extralines!g;
  5011.     
  5012.     return ($tmpl);
  5013. }
  5014.  
  5015.  
  5016. # Utility function; returns content of a URL
  5017. sub getpage {
  5018.     my ($this, $url, $dontdie) = @_;
  5019.  
  5020.     my $failed = 0;
  5021.     my $page = undef;
  5022.     # Try it first to retrieve the page with the "wget" shell command
  5023.     if (-x $sysdeps->{'wget'}) {
  5024.     if (open PAGE, "$sysdeps->{'wget'} $url -O - 2>/dev/null |") {
  5025.         $page = join('', <PAGE>);
  5026.         close PAGE;
  5027.     } else {
  5028.         $failed = 1;
  5029.     }
  5030.     # Then try to retrieve the page with the "curl" shell command
  5031.     } elsif (-x $sysdeps->{'curl'}) {
  5032.     if (open PAGE, "$sysdeps->{'curl'} $url -o - 2>/dev/null |") {
  5033.         $page = join('', <PAGE>);
  5034.         close PAGE;
  5035.     } else {
  5036.         $failed = 1;
  5037.     }
  5038.     } else {
  5039.     warn("WARNING: No tool for downloading web content found, please install either\n\"wget\" or \"curl\"! The result you got may be incorrect!\n");
  5040.     }
  5041.  
  5042.     if ((!$page) || ($failed)) {
  5043.     if ($dontdie) {
  5044.         return undef;
  5045.     } else {
  5046.         die ("http error: " . $url . "\n");
  5047.     }
  5048.     }
  5049.  
  5050.     return $page;
  5051. }
  5052.  
  5053. # Determine the margins as needed by "*ImageableArea"
  5054. sub getmarginsformarginrecord {
  5055.     my ($margins, $width, $height, $pagesize) = @_;
  5056.     if (!defined($margins)) {
  5057.     # No margins defined? Return invalid margins
  5058.     return (undef, undef, undef, undef);
  5059.     }
  5060.     # Defaults
  5061.     my $unit = 'pt';
  5062.     my $absolute = 0;
  5063.     my ($left, $right, $top, $bottom) = (undef, undef, undef, undef);
  5064.     # Check the general margins and then the particular paper size
  5065.     for my $i ('_general', $pagesize) {
  5066.     # Skip a section if it is not defined
  5067.     next if (!defined($margins->{$i}));
  5068.     # Determine the factor to calculate the margin in points (pt)
  5069.     $unit = (defined($margins->{$i}{'unit'}) ?
  5070.          $margins->{$i}{'unit'} : $unit);
  5071.     my $unitfactor = 1.0; # Default unit is points
  5072.     if ($unit =~ /^p/i) {
  5073.         $unitfactor = 1.0;
  5074.     } elsif ($unit =~ /^in/i) {
  5075.         $unitfactor = 72.0;
  5076.     } elsif ($unit =~ /^cm$/i) {
  5077.         $unitfactor = 72.0/2.54;
  5078.     } elsif ($unit =~ /^mm$/i) {
  5079.         $unitfactor = 72.0/25.4;
  5080.     } elsif ($unit =~ /^dots(\d+)dpi$/i) {
  5081.         $unitfactor = 72.0/$1;
  5082.     }
  5083.     # Convert the values to points
  5084.     ($left, $right, $top, $bottom) =
  5085.         ((defined($margins->{$i}{'left'}) ?
  5086.           $margins->{$i}{'left'} * $unitfactor : $left),
  5087.          (defined($margins->{$i}{'right'}) ?
  5088.           $margins->{$i}{'right'} * $unitfactor : $right),
  5089.          (defined($margins->{$i}{'top'}) ?
  5090.           $margins->{$i}{'top'} * $unitfactor : $top),
  5091.          (defined($margins->{$i}{'bottom'}) ?
  5092.           $margins->{$i}{'bottom'} * $unitfactor : $bottom));
  5093.     # Determine the absolute values
  5094.     $absolute = (defined($margins->{$i}{'absolute'}) ?
  5095.              $margins->{$i}{'absolute'} : $absolute);
  5096.     if (!$absolute){
  5097.         if (defined($margins->{$i}{'right'})) {
  5098.         $right = $width - $right;
  5099.         }
  5100.         if (defined($margins->{$i}{'top'})) {
  5101.         $top = $height - $top;
  5102.         }
  5103.     }
  5104.     }
  5105.     $left = sprintf("%.2f", $left) if $left =~ /\./;
  5106.     $right = sprintf("%.2f", $right) if $right =~ /\./;
  5107.     $top = sprintf("%.2f", $top) if $top =~ /\./;
  5108.     $bottom = sprintf("%.2f", $bottom) if $bottom =~ /\./;
  5109.     return ($left, $right, $top, $bottom);
  5110. }
  5111.  
  5112. sub getmargins {
  5113.     my ($dat, $width, $height, $pagesize) = @_;
  5114.     # Determine the unprintable margins
  5115.     my ($left, $right, $top, $bottom) = (undef, undef, undef, undef);
  5116.     # Margins from printer database entry
  5117.     my ($pleft, $pright, $ptop, $pbottom) =
  5118.     getmarginsformarginrecord($dat->{'printermargins'}, 
  5119.                   $width, $height, $pagesize);
  5120.     # Margins from driver database entry
  5121.     my ($dleft, $dright, $dtop, $dbottom) =
  5122.     getmarginsformarginrecord($dat->{'drivermargins'}, 
  5123.                   $width, $height, $pagesize);
  5124.     # Margins from printer/driver combo
  5125.     my ($cleft, $cright, $ctop, $cbottom) =
  5126.     getmarginsformarginrecord($dat->{'combomargins'}, 
  5127.                   $width, $height, $pagesize);
  5128.     # Left margin
  5129.     if (defined($pleft)) {$left = $pleft};
  5130.     if (defined($dleft) &&
  5131.     (!defined($left) || ($dleft > $left))) {$left = $dleft};
  5132.     if (defined($cleft) &&
  5133.     (!defined($left) || ($cleft > $left))) {$left = $cleft};
  5134.     # Right margin
  5135.     if (defined($pright)) {$right = $pright};
  5136.     if (defined($dright) &&
  5137.     (!defined($right) || ($dright < $right))) {$right = $dright};
  5138.     if (defined($cright) &&
  5139.     (!defined($right) || ($cright < $right))) {$right = $cright};
  5140.     # Top margin
  5141.     if (defined($ptop)) {$top = $ptop};
  5142.     if (defined($dtop) &&
  5143.     (!defined($top) || ($dtop < $top))) {$top = $dtop};
  5144.     if (defined($ctop) &&
  5145.     (!defined($top) || ($ctop < $top))) {$top = $ctop};
  5146.     # Bottom margin
  5147.     if (defined($pbottom)) {$bottom = $pbottom};
  5148.     if (defined($dbottom) &&
  5149.     (!defined($bottom) || ($dbottom > $bottom))) {$bottom = $dbottom};
  5150.     if (defined($cbottom) &&
  5151.     (!defined($bottom) || ($dbottom > $bottom))) {$bottom = $cbottom};
  5152.     # Safe margins when margin info is missing
  5153.     my $tborder = 36;
  5154.     my $bborder = 36;
  5155.     my $lborder = 18;
  5156.     my $rborder = 18;
  5157.     $left = $lborder if !defined($left);
  5158.     $right = $width - $rborder if !defined($right);
  5159.     $top = $height - $tborder if !defined($top);
  5160.     $bottom = $bborder if !defined($bottom);
  5161.     # If we entered with $width == 0 and $height == 0, we mean
  5162.     # relative margins, so correct the signs
  5163.     if ($width == 0) {$right = -$right};
  5164.     if ($height == 0) {$top = -$top};
  5165.     # Clean up output
  5166.     $left =~ s/^\s*-0\s*$/0/;
  5167.     $right =~ s/^\s*-0\s*$/0/;
  5168.     $top =~ s/^\s*-0\s*$/0/;
  5169.     $bottom =~ s/^\s*-0\s*$/0/;
  5170.     # Return the results
  5171.     return ($left, $right, $top, $bottom);
  5172. }
  5173.  
  5174. # Generate a translation/longname from a shortname
  5175. sub longname {
  5176.     my $shortname = $_[0];
  5177.     # A space before every upper-case letter in the middle preceeded by
  5178.     # a lower-case one
  5179.     $shortname =~ s/([a-z])([A-Z])/$1 $2/g;
  5180.     # If there are three or more upper-case letters, assume the last as
  5181.     # the beginning of the next word, the others as an abbreviation
  5182.     $shortname =~ s/([A-Z][A-Z]+)([A-Z][a-z])/$1 $2/g;
  5183.     return $shortname;
  5184. }
  5185.  
  5186. # Prepare strings for being part of an HTML document by, converting
  5187. # "<" to "<", ">" to ">", "&" to "&", "\"" to """,
  5188. # and "'" to  "'"
  5189. sub htmlify {
  5190.     my $str = $_[0];
  5191.     $str =~ s!&!&!g;
  5192.     $str =~ s/\</\</g;
  5193.     $str =~ s/\>/\>/g;
  5194.     $str =~ s/\"/\"/g;
  5195.     $str =~ s/\'/\'/g;
  5196.     return $str;
  5197. }
  5198.  
  5199. # This splits RIP directives (PostScript comments which are
  5200. # foomatic-rip uses to build the RIP command line) into multiple lines
  5201. # of a fixed length, to avoid lines longer than 255 characters. The
  5202. # PPD specification does not allow such long lines.
  5203. sub ripdirective {
  5204.     my ($header, $content) = ($_[0], htmlify($_[1]));
  5205.     # If possible, make lines of this length
  5206.     my $maxlength = 72;
  5207.     # Header of continuation line
  5208.     my $continueheader = "";
  5209.     # Two subsequent ampersands are not possible in an htmlified string,
  5210.     # so we can use them at the line end to mark that the current line
  5211.     # continues on the next line. A newline without this is also a newline
  5212.     # in the decoded string
  5213.     my $continuelineend = "&&";
  5214.     # output string
  5215.     my $out;
  5216.     # The colon and the quote after the header must be on the line with
  5217.     # the header
  5218.     $header .= ": \"";
  5219.     # How much of the current line is left?
  5220.     my $freelength = $maxlength - length($header) -
  5221.     length($continuelineend);
  5222.     # Add the header
  5223.     if ($freelength < 0) {
  5224.     # header longer than $maxlength, don't break it
  5225.     $out = "$header$continuelineend\n$continueheader";
  5226.     $freelength = $maxlength - length($continueheader) -
  5227.         length($continuelineend);
  5228.     } else {
  5229.     $out = "$header";
  5230.     }
  5231.     $content .= "\"";
  5232.     # Go through every line of the $content
  5233.     for my $l (split ("\n", $content)) {
  5234.     while ($l) {
  5235.         # Take off $maxlength portions until the string is used up
  5236.         if (length($l) < $freelength) {
  5237.         $freelength = length($l);
  5238.         }
  5239.         my $line = substr($l, 0, $freelength, "");
  5240.         # Add the portion 
  5241.         $out .= $line;
  5242.         # Finish the line
  5243.         $freelength = $maxlength - length($continueheader) -
  5244.         length($continuelineend);
  5245.         if ($l) {
  5246.         # Line continues in next line
  5247.         $out .= "$continuelineend\n$continueheader";
  5248.         } else {
  5249.         # line ends
  5250.         $out .= "\n";
  5251.         last;
  5252.         }
  5253.     }
  5254.     }
  5255.     # Remove trailing newline
  5256.     $out = substr($out, 0, -1);
  5257.     return $out;
  5258. }
  5259.  
  5260.  
  5261. # PPD boilerplate template
  5262.  
  5263. sub get_tmpl_paperdimension {
  5264.     return <<ENDPDTEMPL;
  5265. *% Generic PaperDimension; evidently there was no normal PageSize argument
  5266.  
  5267. *DefaultPaperDimension: Letter
  5268. *PaperDimension Letter:    "612 792"
  5269. *PaperDimension Legal:    "612 1008"
  5270. *PaperDimension A4:    "595 842"
  5271. ENDPDTEMPL
  5272. }
  5273.  
  5274. sub get_tmpl {
  5275.     return <<ENDTMPL;
  5276. *PPD-Adobe: "4.3"
  5277. \@\@POSTPIPE\@\@*%
  5278. \@\@HEADCOMMENT\@\@
  5279. *%
  5280. *% You may save this file as '\@\@SAVETHISAS\@\@'
  5281. *%
  5282. *%
  5283. *FormatVersion:    "4.3"
  5284. *FileVersion:    "1.1"
  5285. *LanguageVersion: English 
  5286. *LanguageEncoding: ISOLatin1
  5287. *PCFileName:    "\@\@PCFILENAME\@\@.PPD"
  5288. *Manufacturer:    "\@\@MANUFACTURER\@\@"
  5289. *Product:    "(\@\@PNPMODEL\@\@)"
  5290. *cupsVersion:    1.0
  5291. *cupsManualCopies: True
  5292. *cupsModelNumber:  2
  5293. *cupsFilter:    "application/vnd.cups-postscript 100 foomatic-rip"
  5294. *cupsFilter:    "application/vnd.cups-pdf 0 foomatic-rip"
  5295. *cupsFilter:    "application/vnd.apple-pdf 25 foomatic-rip"
  5296. *%pprRIP:        foomatic-rip other
  5297. *ModelName:     "\@\@MODEL\@\@"
  5298. *ShortNickName: "\@\@SHORTNICKNAME\@\@"
  5299. *NickName:      "\@\@NICKNAME\@\@"
  5300. *PSVersion:    "(3010.000) 550"
  5301. *PSVersion:    "(3010.000) 651"
  5302. *PSVersion:    "(3010.000) 652"
  5303. *PSVersion:    "(3010.000) 653"
  5304. *PSVersion:    "(3010.000) 704"
  5305. *PSVersion:    "(3010.000) 705"
  5306. *PSVersion:    "(3010.000) 800"
  5307. *PSVersion:    "(3010.000) 815"
  5308. *PSVersion:    "(3010.000) 850"
  5309. *PSVersion:    "(3010.000) 860"
  5310. *PSVersion:    "(3010.000) 861"
  5311. *PSVersion:    "(3010.000) 862"
  5312. *PSVersion:    "(3010.000) 863"
  5313. *PSVersion:    "(3010.000) 864"
  5314. *PSVersion:    "(3010.000) 870"
  5315. *LanguageLevel:    "3"
  5316. \@\@COLOR\@\@
  5317. *FileSystem:    False
  5318. *Throughput:    "1"
  5319. *LandscapeOrientation: Plus90
  5320. *TTRasterizer:    Type42
  5321. \@\@IEEE1284\@\@
  5322. \@\@DRIVERPROPERTIES\@\@
  5323. \@\@EXTRALINES\@\@
  5324. \@\@OTHERSTUFF\@\@
  5325.  
  5326. \@\@OPTIONS\@\@
  5327.  
  5328. *% Generic boilerplate PPD stuff as standard PostScript fonts and so on
  5329.  
  5330. *DefaultFont: Courier
  5331. *Font AvantGarde-Book: Standard "(001.006S)" Standard ROM
  5332. *Font AvantGarde-BookOblique: Standard "(001.006S)" Standard ROM
  5333. *Font AvantGarde-Demi: Standard "(001.007S)" Standard ROM
  5334. *Font AvantGarde-DemiOblique: Standard "(001.007S)" Standard ROM
  5335. *Font Bookman-Demi: Standard "(001.004S)" Standard ROM
  5336. *Font Bookman-DemiItalic: Standard "(001.004S)" Standard ROM
  5337. *Font Bookman-Light: Standard "(001.004S)" Standard ROM
  5338. *Font Bookman-LightItalic: Standard "(001.004S)" Standard ROM
  5339. *Font Courier: Standard "(002.004S)" Standard ROM
  5340. *Font Courier-Bold: Standard "(002.004S)" Standard ROM
  5341. *Font Courier-BoldOblique: Standard "(002.004S)" Standard ROM
  5342. *Font Courier-Oblique: Standard "(002.004S)" Standard ROM
  5343. *Font Helvetica: Standard "(001.006S)" Standard ROM
  5344. *Font Helvetica-Bold: Standard "(001.007S)" Standard ROM
  5345. *Font Helvetica-BoldOblique: Standard "(001.007S)" Standard ROM
  5346. *Font Helvetica-Narrow: Standard "(001.006S)" Standard ROM
  5347. *Font Helvetica-Narrow-Bold: Standard "(001.007S)" Standard ROM
  5348. *Font Helvetica-Narrow-BoldOblique: Standard "(001.007S)" Standard ROM
  5349. *Font Helvetica-Narrow-Oblique: Standard "(001.006S)" Standard ROM
  5350. *Font Helvetica-Oblique: Standard "(001.006S)" Standard ROM
  5351. *Font NewCenturySchlbk-Bold: Standard "(001.009S)" Standard ROM
  5352. *Font NewCenturySchlbk-BoldItalic: Standard "(001.007S)" Standard ROM
  5353. *Font NewCenturySchlbk-Italic: Standard "(001.006S)" Standard ROM
  5354. *Font NewCenturySchlbk-Roman: Standard "(001.007S)" Standard ROM
  5355. *Font Palatino-Bold: Standard "(001.005S)" Standard ROM
  5356. *Font Palatino-BoldItalic: Standard "(001.005S)" Standard ROM
  5357. *Font Palatino-Italic: Standard "(001.005S)" Standard ROM
  5358. *Font Palatino-Roman: Standard "(001.005S)" Standard ROM
  5359. *Font Symbol: Special "(001.007S)" Special ROM
  5360. *Font Times-Bold: Standard "(001.007S)" Standard ROM
  5361. *Font Times-BoldItalic: Standard "(001.009S)" Standard ROM
  5362. *Font Times-Italic: Standard "(001.007S)" Standard ROM
  5363. *Font Times-Roman: Standard "(001.007S)" Standard ROM
  5364. *Font ZapfChancery-MediumItalic: Standard "(001.007S)" Standard ROM
  5365. *Font ZapfDingbats: Special "(001.004S)" Standard ROM
  5366.  
  5367. ENDTMPL
  5368. }
  5369.  
  5370. # Determine the paper width and height in points from a given paper size
  5371. # name. Used for the "PaperDimension" and "ImageableArea" entries in PPD
  5372. # files.
  5373. #
  5374. # The paper sizes in the list are all sizes known to Ghostscript, all
  5375. # of Gutenprint, all sizes of HPIJS, and some others found in the data
  5376. # of printer drivers.
  5377.  
  5378. sub getpapersize {
  5379.     my $papersize = lc(join('', @_));
  5380.  
  5381.     my @sizetable = (
  5382.     ['germanlegalfanfold', '612 936'],
  5383.     ['halfletter',         '396 612'],
  5384.     ['letterwide',         '647 957'],
  5385.     ['lettersmall',        '612 792'],
  5386.     ['letter',             '612 792'],
  5387.     ['legal',              '612 1008'],
  5388.     ['postcard',           '283 416'],
  5389.     ['tabloid',            '792 1224'],
  5390.     ['ledger',             '1224 792'],
  5391.     ['tabloidextra',       '864 1296'],
  5392.     ['statement',          '396 612'],
  5393.     ['manual',             '396 612'],
  5394.     ['executive',          '522 756'],
  5395.     ['folio',              '612 936'],
  5396.     ['archa',              '648 864'],
  5397.     ['archb',              '864 1296'],
  5398.     ['archc',              '1296 1728'],
  5399.     ['archd',              '1728 2592'],
  5400.     ['arche',              '2592 3456'],
  5401.     ['usaarch',            '648 864'],
  5402.     ['usbarch',            '864 1296'],
  5403.     ['uscarch',            '1296 1728'],
  5404.     ['usdarch',            '1728 2592'],
  5405.     ['usearch',            '2592 3456'],
  5406.     ['a2.*invit.*',        '315 414'],
  5407.     ['b6-c4',              '354 918'],
  5408.     ['c7-6',               '229 459'],
  5409.     ['supera3-b',          '932 1369'],
  5410.     ['a3wide',             '936 1368'],
  5411.     ['a4wide',             '633 1008'],
  5412.     ['a4small',            '595 842'],
  5413.     ['sra4',               '637 907'],
  5414.     ['sra3',               '907 1275'],
  5415.     ['sra2',               '1275 1814'],
  5416.     ['sra1',               '1814 2551'],
  5417.     ['sra0',               '2551 3628'],
  5418.     ['ra4',                '609 864'],
  5419.     ['ra3',                '864 1218'],
  5420.     ['ra2',                '1218 1729'],
  5421.     ['ra1',                '1729 2437'],
  5422.     ['ra0',                '2437 3458'],
  5423.     ['a10',                '74 105'],
  5424.     ['a9',                 '105 148'],
  5425.     ['a8',                 '148 210'],
  5426.     ['a7',                 '210 297'],
  5427.     ['a6',                 '297 420'],
  5428.     ['a5',                 '420 595'],
  5429.     ['a4',                 '595 842'],
  5430.     ['a3',                 '842 1191'],
  5431.     ['a2',                 '1191 1684'],
  5432.     ['a1',                 '1684 2384'],
  5433.     ['a0',                 '2384 3370'],
  5434.     ['2a',                 '3370 4768'],
  5435.     ['4a',                 '4768 6749'],
  5436.     ['c10',                '79 113'],
  5437.     ['c9',                 '113 161'],
  5438.     ['c8',                 '161 229'],
  5439.     ['c7',                 '229 323'],
  5440.     ['c6',                 '323 459'],
  5441.     ['c5',                 '459 649'],
  5442.     ['c4',                 '649 918'],
  5443.     ['c3',                 '918 1298'],
  5444.     ['c2',                 '1298 1836'],
  5445.     ['c1',                 '1836 2599'],
  5446.     ['c0',                 '2599 3676'],
  5447.     ['b10.*jis',           '90 127'],
  5448.     ['b9.*jis',            '127 180'],
  5449.     ['b8.*jis',            '180 257'],
  5450.     ['b7.*jis',            '257 362'],
  5451.     ['b6.*jis',            '362 518'],
  5452.     ['b5.*jis',            '518 727'],
  5453.     ['b4.*jis',            '727 1029'],
  5454.     ['b3.*jis',            '1029 1459'],
  5455.     ['b2.*jis',            '1459 2063'],
  5456.     ['b1.*jis',            '2063 2919'],
  5457.     ['b0.*jis',            '2919 4127'],
  5458.     ['jis.*b10',           '90 127'],
  5459.     ['jis.*b9',            '127 180'],
  5460.     ['jis.*b8',            '180 257'],
  5461.     ['jis.*b7',            '257 362'],
  5462.     ['jis.*b6',            '362 518'],
  5463.     ['jis.*b5',            '518 727'],
  5464.     ['jis.*b4',            '727 1029'],
  5465.     ['jis.*b3',            '1029 1459'],
  5466.     ['jis.*b2',            '1459 2063'],
  5467.     ['jis.*b1',            '2063 2919'],
  5468.     ['jis.*b0',            '2919 4127'],
  5469.     ['b10.*iso',           '87 124'],
  5470.     ['b9.*iso',            '124 175'],
  5471.     ['b8.*iso',            '175 249'],
  5472.     ['b7.*iso',            '249 354'],
  5473.     ['b6.*iso',            '354 498'],
  5474.     ['b5.*iso',            '498 708'],
  5475.     ['b4.*iso',            '708 1000'],
  5476.     ['b3.*iso',            '1000 1417'],
  5477.     ['b2.*iso',            '1417 2004'],
  5478.     ['b1.*iso',            '2004 2834'],
  5479.     ['b0.*iso',            '2834 4008'],
  5480.     ['2b.*iso',            '4008 5669'],
  5481.     ['4b.*iso',            '5669 8016'],
  5482.     ['iso.*b10',           '87 124'],
  5483.     ['iso.*b9',            '124 175'],
  5484.     ['iso.*b8',            '175 249'],
  5485.     ['iso.*b7',            '249 354'],
  5486.     ['iso.*b6',            '354 498'],
  5487.     ['iso.*b5',            '498 708'],
  5488.     ['iso.*b4',            '708 1000'],
  5489.     ['iso.*b3',            '1000 1417'],
  5490.     ['iso.*b2',            '1417 2004'],
  5491.     ['iso.*b1',            '2004 2834'],
  5492.     ['iso.*b0',            '2834 4008'],
  5493.     ['iso.*2b',            '4008 5669'],
  5494.     ['iso.*4b',            '5669 8016'],
  5495.     ['b10envelope',        '87 124'],
  5496.     ['b9envelope',         '124 175'],
  5497.     ['b8envelope',         '175 249'],
  5498.     ['b7envelope',         '249 354'],
  5499.     ['b6envelope',         '354 498'],
  5500.     ['b5envelope',         '498 708'],
  5501.     ['b4envelope',         '708 1000'],
  5502.     ['b3envelope',         '1000 1417'],
  5503.     ['b2envelope',         '1417 2004'],
  5504.     ['b1envelope',         '2004 2834'],
  5505.     ['b0envelope',         '2834 4008'],
  5506.     ['b10',                '87 124'],
  5507.     ['b9',                 '124 175'],
  5508.     ['b8',                 '175 249'],
  5509.     ['b7',                 '249 354'],
  5510.     ['b6',                 '354 498'],
  5511.     ['b5',                 '498 708'],
  5512.     ['b4',                 '708 1000'],
  5513.     ['b3',                 '1000 1417'],
  5514.     ['b2',                 '1417 2004'],
  5515.     ['b1',                 '2004 2834'],
  5516.     ['b0',                 '2834 4008'],
  5517.     ['monarch',            '279 540'],
  5518.     ['dl',                 '311 623'],
  5519.     ['com10',              '297 684'],
  5520.     ['com.*10',            '297 684'],
  5521.     ['env10',              '297 684'],
  5522.     ['env.*10',            '297 684'],
  5523.     ['hagaki',             '283 420'],
  5524.     ['oufuku',             '420 567'],
  5525.     ['kaku',               '680 941'],
  5526.     ['long.*3',            '340 666'],
  5527.     ['long.*4',            '255 581'],
  5528.     ['foolscap',           '576 936'],
  5529.     ['flsa',               '612 936'],
  5530.     ['flse',               '648 936'],
  5531.     ['photo100x150',       '283 425'],
  5532.     ['photo200x300',       '567 850'],
  5533.     ['photofullbleed',     '298 440'],
  5534.     ['photo4x6',           '288 432'],
  5535.     ['photo',              '288 432'],
  5536.     ['wide',               '977 792'],
  5537.     ['card148',            '419 297'],
  5538.     ['envelope132x220',    '374 623'],
  5539.     ['envelope61/2',       '468 260'],
  5540.     ['supera',             '644 1008'],
  5541.     ['superb',             '936 1368'],
  5542.     ['fanfold5',           '612 792'],
  5543.     ['fanfold4',           '612 864'],
  5544.     ['fanfold3',           '684 792'],
  5545.     ['fanfold2',           '864 612'],
  5546.     ['fanfold1',           '1044 792'],
  5547.     ['fanfold',            '1071 792'],
  5548.     ['panoramic',          '595 1683'],
  5549.     ['plotter.*size.*a',   '612 792'],
  5550.     ['plotter.*size.*b',   '792 1124'],
  5551.     ['plotter.*size.*c',   '1124 1584'],
  5552.     ['plotter.*size.*d',   '1584 2448'],
  5553.     ['plotter.*size.*e',   '2448 3168'],
  5554.     ['plotter.*size.*f',   '3168 4896'],
  5555.     ['archlarge',          '162 540'],
  5556.     ['standardaddr',       '81 252'],
  5557.     ['largeaddr',          '101 252'],
  5558.     ['suspensionfile',     '36 144'],
  5559.     ['videospine',         '54 423'],
  5560.     ['badge',              '153 288'],
  5561.     ['archsmall',          '101 540'],
  5562.     ['videotop',           '130 223'],
  5563.     ['diskette',           '153 198'],
  5564.     ['76\.2mmroll',        '216 0'],
  5565.     ['69\.5mmroll',        '197 0'],
  5566.     ['roll',               '612 0'],
  5567.     ['custom',             '0 0']
  5568.     );
  5569.  
  5570.     # Remove prefixes which sometimes could appear
  5571.     $papersize =~ s/form_//;
  5572.  
  5573.     # Check whether the paper size name is in the list above
  5574.     for my $item (@sizetable) {
  5575.     if ($papersize =~ /@{$item}[0]/) {
  5576.         return @{$item}[1];
  5577.     }
  5578.     }
  5579.  
  5580.     # Check if we have a "<Width>x<Height>" format, assume the numbers are
  5581.     # given in inches
  5582.     if ($papersize =~ /(\d+)x(\d+)/) {
  5583.     my $w = $1 * 72;
  5584.     my $h = $2 * 72;
  5585.     return sprintf("%d %d", $w, $h);
  5586.     }
  5587.  
  5588.     # Check if we have a "w<Width>h<Height>" format, assume the numbers are
  5589.     # given in points
  5590.     if ($papersize =~ /w(\d+)h(\d+)/) {
  5591.     return "$1 $2";
  5592.     }
  5593.  
  5594.     # Check if we have a "w<Width>" format, assume roll paper with the given
  5595.     # width in points
  5596.     if ($papersize =~ /w(\d+)/) {
  5597.     return "$1 0";
  5598.     }
  5599.  
  5600.     # This paper size is absolutely unknown, issue a warning
  5601.     warn "WARNING: Unknown paper size: $papersize!";
  5602.     return "0 0";
  5603. }
  5604.  
  5605. # Get documentation for the printer/driver pair to print out. For
  5606. # "Execution Details" section of driver web pages on OpenPrinting
  5607.  
  5608. sub getexecdocs {
  5609.  
  5610.     my ($this) = $_[0];
  5611.  
  5612.     my $dat = $this->{'dat'};
  5613.  
  5614.     my @docs;
  5615.     
  5616.     # Construct the proper command line.
  5617.     my $commandline = htmlify($dat->{'cmd'});
  5618.  
  5619.     if ($commandline eq "") {return ();}
  5620.  
  5621.     my @letters = qw/A B C D E F G H I J K L M Z/;
  5622.     
  5623.     for my $spot (@letters) {
  5624.     
  5625.     if($commandline =~ m!\%$spot!) {
  5626.  
  5627.         my $arg;
  5628.       argument:
  5629.         for $arg (@{$dat->{'args'}}) {
  5630. #        for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  5631. #              @{$dat->{'args'}}) {
  5632.         
  5633.         # Only do arguments that go in this spot
  5634.         next argument if ($arg->{'spot'} ne $spot);
  5635.         # PJL arguments are not inserted at a spot in the command
  5636.         # line
  5637.         next argument if ($arg->{'style'} eq 'J');
  5638.         # Composite options are not interesting here
  5639.         next argument if ($arg->{'style'} eq 'X');
  5640.         
  5641.         my $name = htmlify($arg->{'name'});
  5642.         my $varname = htmlify($arg->{'varname'});
  5643.         my $cmd = htmlify($arg->{'proto'});
  5644.         my $comment = htmlify($arg->{'comment'});
  5645.         my $placeholder = "</TT><I><$name></I><TT>";
  5646.         my $default = htmlify($arg->{'default'});
  5647.         my $type = $arg->{'type'};
  5648.         my $cmdvar = "";
  5649.         my $gsarg1 = "";
  5650.         my $gsarg2 = "";
  5651.         if ($arg->{'style'} eq 'G') {
  5652.             $gsarg1 = ' -c "';
  5653.             $gsarg2 = '"';
  5654.             $cmd =~ s/\"/\\\"/g;
  5655.         }
  5656.         #my $leftbr = ($arg->{'required'} ? "" : "[");
  5657.         #my $rightbr = ($arg->{'required'} ? "" : "]");
  5658.         my $leftbr = "";
  5659.         my $rightbr = "";
  5660.     
  5661.         if ($type eq 'bool') {
  5662.             $cmdvar = "$leftbr$gsarg1$cmd$gsarg2$rightbr";
  5663.         } elsif ($type eq 'int' or $type eq 'float') {
  5664.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",$placeholder);
  5665.         } elsif ($type eq 'enum') {
  5666.             my $val;
  5667.             if ($val=valbyname($arg,$default)) {
  5668.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",
  5669.                       $placeholder);
  5670.             }
  5671.         }
  5672.         
  5673.         # Insert the processed argument in the commandline
  5674.         # just before every occurance of the spot marker.
  5675.         $cmdvar =~ s!^\[\ !\ \[!;
  5676.         $commandline =~ s!\%$spot!$cmdvar\%$spot!g;
  5677.         }
  5678.         
  5679.         # Remove the letter markers from the commandline
  5680.         $commandline =~ s!\%$spot!!g;
  5681.         
  5682.     }
  5683.     
  5684.     }
  5685.  
  5686.     $dat->{'excommandline'} = $commandline;
  5687.  
  5688.     push(@docs, "<B>Command Line</B><P>");
  5689.     push(@docs, "<BLOCKQUOTE><TT>$commandline</TT></BLOCKQUOTE><P>");
  5690.  
  5691.     my ($arg, @doctmp);
  5692.     my @pjlcommands = ();
  5693.   argt:
  5694.     for $arg (@{$dat->{'args'}}) {
  5695. #    for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  5696. #          @{$dat->{'args'}}) {
  5697.  
  5698.     # Composite options are not interesting here
  5699.     next argt if ($arg->{'style'} eq 'X');
  5700.  
  5701.     # Make sure that the longname/translation exists
  5702.     if (!$arg->{'comment'}) {
  5703.         $arg->{'comment'} = longname($arg->{'name'});
  5704.     }
  5705.  
  5706.     my $name = htmlify($arg->{'name'});
  5707.     my $cmd = htmlify($arg->{'proto'});
  5708.     my $comment = htmlify($arg->{'comment'});
  5709.     my $placeholder = "</TT><I><$name></I><TT>";
  5710.     if ($arg->{'style'} eq 'J') {
  5711.         $cmd = "\@PJL $cmd";
  5712.         my $sprintfcmd = $cmd;
  5713.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5714.         push (@pjlcommands, sprintf($sprintfcmd, $placeholder));
  5715.     }
  5716.  
  5717.     my $default = htmlify($arg->{'default'});
  5718.     my $type = $arg->{'type'};
  5719.     
  5720.     my $required = ($arg->{'required'} ? " required" : "n optional");
  5721.     my $pjl = ($arg->{'style'} eq 'J' ? "PJL " : "");
  5722.  
  5723.     if ($type eq 'bool') {
  5724.         my $name_false = htmlify($arg->{'name_false'});
  5725.         push(@doctmp,
  5726.          "<DL><DT><I>$name</I></DT>",
  5727.          "<DD>A$required boolean ${pjl}argument meaning $name if present or $name_false if not.<BR>",
  5728.          "$comment<BR>",
  5729.          "Prototype: <TT>$cmd</TT><BR>",
  5730.          "Default: ", $default ? "True" : "False",
  5731.          "</DD></DL><P>"
  5732.          );
  5733.  
  5734.     } elsif ($type eq 'int' or $type eq 'float') {
  5735.         my $max = (defined($arg->{'max'}) ? $arg->{'max'} : "none");
  5736.         my $min = (defined($arg->{'min'}) ? $arg->{'min'} : "none");
  5737.         my $sprintfcmd = $cmd;
  5738.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5739.         push(@doctmp,
  5740.          "<DL><DT><I>$name</I></DT>",
  5741.          "<DD>A$required $type ${pjl}argument.<BR>",
  5742.          "$comment<BR>",
  5743.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  5744.          "</TT><BR>",
  5745.          "Default: <TT>$default</TT><BR>",
  5746.          "Range: <TT>$min <= $placeholder <= $max</TT>",
  5747.          "</DD></DL><P>"
  5748.          );
  5749.  
  5750.     } elsif ($type eq 'enum') {
  5751.         my ($val, $defstr);
  5752.         my (@choicelist) = ();
  5753.  
  5754.         for $val (@{$arg->{'vals'}}) {
  5755.  
  5756.         # Make sure that the longname/translation exists
  5757.         if (!$val->{'comment'}) {
  5758.             $val->{'comment'} = longname($val->{'value'});
  5759.         }
  5760.  
  5761.         my ($value, $comment, $driverval) = 
  5762.             (htmlify($val->{'value'}),
  5763.              htmlify($val->{'comment'}),
  5764.              htmlify($val->{'driverval'}));
  5765.  
  5766.         if (defined($driverval)) {
  5767.             if ($driverval eq "") {
  5768.             push(@choicelist,
  5769.                  "<LI>$value: $comment (<TT>$placeholder</TT> is left blank)</LI>");
  5770.             } else {
  5771.             my $widthheight = "";
  5772.             if (($name eq "PageSize") && ($value eq "Custom")) {
  5773.                 my $width = "</TT><I><Width></I><TT>";
  5774.                 my $height = "</TT><I><Height></I><TT>";
  5775.                 $driverval =~ s/\%0/$width/ or
  5776.                             $driverval =~ s/(\W)0(\W)/$1$width$2/ or
  5777.                             $driverval =~ s/^0(\W)/$width$1/m or
  5778.                             $driverval =~ s/(\W)0$/$1$width/m or
  5779.                             $driverval =~ s/^0$/$width/m;
  5780.                             $driverval =~ s/\%1/$height/ or
  5781.                             $driverval =~ s/(\W)0(\W)/$1$height$2/ or
  5782.                             $driverval =~ s/^0(\W)/$height$1/m or
  5783.                             $driverval =~ s/(\W)0$/$1$height/m or
  5784.                             $driverval =~ s/^0$/$height/m;
  5785.                 $widthheight = ", <I><Width></I> and <I><Height></I> are the page dimensions in points, 1/72 inches";
  5786.             }
  5787.             push(@choicelist,
  5788.                  "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$driverval</TT>'$widthheight)</LI>");
  5789.             }
  5790.         } else {
  5791.             push(@choicelist,
  5792.              "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$value</TT>')</LI>");
  5793.         }
  5794.         }
  5795.  
  5796.         my $sprintfcmd = $cmd;
  5797.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5798.         push(@doctmp,
  5799.          "<DL><DT><I>$name</I></DT>",
  5800.          "<DD>A$required enumerated choice ${pjl}argument.<BR>",
  5801.          "$comment<BR>",
  5802.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  5803.          "</TT><BR>",
  5804.          "Default: $default",
  5805.          "<UL>", 
  5806.          join("", @choicelist), 
  5807.          "</UL></DD></DL><P>"
  5808.          );
  5809.  
  5810.     }
  5811.     }
  5812.  
  5813.     # Instructions for PJL commands
  5814.     if (($#pjlcommands > -1) && (defined($dat->{'pjl'}))) {
  5815.     #if (($#pjlcommands > -1)) {
  5816.     my @pjltmp;
  5817.     push(@pjltmp,
  5818.          "PJL arguments are not put into the command line, they must be put into a PJL header which is prepended to the actual job data which is generated by the command line shown above and sent to the printer. After the job data one can reset the printer via PJL. So a complete job looks as follows:<BLOCKQUOTE>",
  5819.          "<I><ESC></I>",
  5820.          # The "JOB" PJL command is not supported by all printers
  5821.          "<TT>%-12345X\@PJL</TT><BR>");
  5822.          #"<TT>%-12345X\@PJL JOB NAME=\"</TT>",
  5823.          #"<I><A job name></I>",
  5824.          #"<TT>\"</TT><BR>");
  5825.     for my $command (@pjlcommands) {
  5826.         push(@pjltmp,
  5827.          "<TT>$command</TT><BR>");
  5828.     }
  5829.     push(@pjltmp,
  5830.          "<I><The job data></I><BR>",
  5831.          "<I><ESC></I>",
  5832.          # The "JOB" PJL command is not supported by all printers
  5833.          "<TT>%-12345X\@PJL RESET</TT></BLOCKQUOTE><P>",
  5834.          #"<TT>%-12345X\@PJL EOJ</TT></BLOCKQUOTE><P>",
  5835.          "<I><ESC></I>",
  5836.          ": This is the ",
  5837.          "<I>ESC</I>",
  5838.          " character, ASCII code 27.<P>",
  5839.          #"<I><A job name></I>",
  5840.          #": The job name can be chosen arbitrarily, some printers show it on their front panel displays.<P>",
  5841.          "It is not required to give the PJL arguments, you can leave out some of them or you can even send only the job data without PJL header and PJL end-of-job mark.<P>");
  5842.     push(@docs, "<B>PJL</B><P>");
  5843.     push(@docs, @pjltmp);
  5844.     } elsif ((defined($dat->{'drivernopjl'})) && 
  5845.          ($dat->{'drivernopjl'} == 1) && 
  5846.          (defined($dat->{'pjl'}))) {
  5847.     my @pjltmp;
  5848.     push(@pjltmp,
  5849.          "This driver produces a PJL header with PJL commands internally and it is incompatible with extra PJL options merged into that header. Therefore there are no PJL options available when using this driver.<P>");
  5850.     push(@docs, "<B>PJL</B><P>");
  5851.     push(@docs, @pjltmp);
  5852.     }
  5853.  
  5854.     push(@docs, "<B>Options</B><P>");
  5855.  
  5856.     push(@docs, @doctmp);
  5857.  
  5858.     return @docs;
  5859.    
  5860. }
  5861.  
  5862. # Get a shorter summary documentation thing.
  5863. sub get_summarydocs {
  5864.     my ($this) = $_[0];
  5865.  
  5866.     my $dat = $this->{'dat'};
  5867.  
  5868.     my @docs;
  5869.  
  5870.     for my $arg (@{$dat->{'args'}}) {
  5871.  
  5872.     # Make sure that the longname/translation exists
  5873.     if (!$arg->{'comment'}) {
  5874.         $arg->{'comment'} = longname($arg->{'name'});
  5875.     }
  5876.  
  5877.     my ($name,
  5878.         $required,
  5879.         $type,
  5880.         $comment,
  5881.         $spot,
  5882.         $default) = ($arg->{'name'},
  5883.              $arg->{'required'},
  5884.              $arg->{'type'},
  5885.              $arg->{'comment'},
  5886.              $arg->{'spot'},
  5887.              $arg->{'default'});
  5888.     
  5889.     my $reqstr = ($required ? " required" : "n optional");
  5890.     push(@docs,
  5891.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  5892.  
  5893.     push(@docs,
  5894.          "  This option corresponds to a PJL command.\n") 
  5895.         if ($spot eq 'Y');
  5896.     
  5897.     if ($type eq 'bool') {
  5898.         if (defined($default)) {
  5899.         my $defstr = ($default ? "True" : "False");
  5900.         push(@docs, "  Default: $defstr\n");
  5901.         }
  5902.         push(@docs, "  Example (true): `$name'\n");
  5903.         push(@docs, "  Example (false): `no$name'\n");
  5904.     } elsif ($type eq 'enum') {
  5905.         push(@docs, "  Possible choices:\n");
  5906.         my $exarg;
  5907.         for (@{$arg->{'vals'}}) {
  5908.  
  5909.         # Make sure that the longname/translation exists
  5910.         if (!$_->{'comment'}) {
  5911.             $_->{'comment'} = longname($_->{'value'});
  5912.         }
  5913.  
  5914.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  5915.         push(@docs, "   * $choice: $comment\n");
  5916.         $exarg=$choice;
  5917.         }
  5918.         if (defined($default)) {
  5919.         push(@docs, "  Default: $default\n");
  5920.         }
  5921.         push(@docs, "  Example: `$name=$exarg'\n");
  5922.     } elsif ($type eq 'int' or $type eq 'float') {
  5923.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  5924.         my $exarg;
  5925.         if (defined($max)) {
  5926.         push(@docs, "  Range: $min <= x <= $max\n");
  5927.         $exarg=$max;
  5928.         }
  5929.         if (defined($default)) {
  5930.         push(@docs, "  Default: $default\n");
  5931.         $exarg=$default;
  5932.         }
  5933.         if (!$exarg) { $exarg=0; }
  5934.         push(@docs, "  Example: `$name=$exarg'\n");
  5935.     }
  5936.  
  5937.     push(@docs, "\n");
  5938.     }
  5939.  
  5940.     return @docs;
  5941.  
  5942. }
  5943.  
  5944. # About as obsolete as the other docs functions.  Why on earth are
  5945. # there three, anyway?!
  5946. sub getdocs {
  5947.     my ($this) = $_[0];
  5948.  
  5949.     my $dat = $this->{'dat'};
  5950.  
  5951.     my @docs;
  5952.  
  5953.     for my $arg (@{$dat->{'args'}}) {
  5954.  
  5955.     # Make sure that the longname/translation exists
  5956.     if (!$arg->{'comment'}) {
  5957.         $arg->{'comment'} = longname($arg->{'name'});
  5958.     }
  5959.  
  5960.     my ($name,
  5961.         $required,
  5962.         $type,
  5963.         $comment,
  5964.         $spot,
  5965.         $default) = ($arg->{'name'},
  5966.              $arg->{'required'},
  5967.              $arg->{'type'},
  5968.              $arg->{'comment'},
  5969.              $arg->{'spot'},
  5970.              $arg->{'default'});
  5971.     
  5972.     my $reqstr = ($required ? " required" : "n optional");
  5973.     push(@docs,
  5974.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  5975.  
  5976.     push(@docs,
  5977.          "  This option corresponds to a PJL command.\n") 
  5978.         if ($spot eq 'Y');
  5979.     
  5980.     if ($type eq 'bool') {
  5981.         if (defined($default)) {
  5982.         my $defstr = ($default ? "True" : "False");
  5983.         push(@docs, "  Default: $defstr\n");
  5984.         }
  5985.         push(@docs, "  Example (true): `$name'\n");
  5986.         push(@docs, "  Example (false): `no$name'\n");
  5987.     } elsif ($type eq 'enum') {
  5988.         push(@docs, "  Possible choices:\n");
  5989.         my $exarg;
  5990.         for (@{$arg->{'vals'}}) {
  5991.  
  5992.         # Make sure that the longname/translation exists
  5993.         if (!$_->{'comment'}) {
  5994.             $_->{'comment'} = longname($_->{'value'});
  5995.         }
  5996.  
  5997.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  5998.         push(@docs, "   * $choice: $comment\n");
  5999.         $exarg=$choice;
  6000.         }
  6001.         if (defined($default)) {
  6002.         push(@docs, "  Default: $default\n");
  6003.         }
  6004.         push(@docs, "  Example: `$name=$exarg'\n");
  6005.     } elsif ($type eq 'int' or $type eq 'float') {
  6006.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  6007.         my $exarg;
  6008.         if (defined($max)) {
  6009.         push(@docs, "  Range: $min <= x <= $max\n");
  6010.         $exarg=$max;
  6011.         }
  6012.         if (defined($default)) {
  6013.         push(@docs, "  Default: $default\n");
  6014.         $exarg=$default;
  6015.         }
  6016.         if (!$exarg) { $exarg=0; }
  6017.         push(@docs, "  Example: `$name=$exarg'\n");
  6018.     }
  6019.  
  6020.     push(@docs, "\n");
  6021.     }
  6022.  
  6023.     return @docs;
  6024.  
  6025. }
  6026.  
  6027. # Find a choice value hash by name.
  6028. # Operates on old dat structure...
  6029. sub valbyname {
  6030.     my ($arg,$name) = @_;
  6031.  
  6032.     my $val;
  6033.     for my $val (@{$arg->{'vals'}}) {
  6034.     return $val if (lc($name) eq lc($val->{'value'}));
  6035.     }
  6036.  
  6037.     return undef;
  6038. }
  6039.  
  6040. # replace numbers with fixed 6-digit number, set to lower case, replace
  6041. # non-alphanumeric characters by single spaces for ease of sorting
  6042. # ie: sort { normalizename($a) cmp normalizename($b) } @foo;
  6043. sub normalizename {
  6044.     my $n = $_[0];
  6045.  
  6046.     $n =~ s/[\d\.]+/sprintf("%013.6f", $&)/eg;
  6047.     $n = normalize($n);
  6048.     return $n;
  6049. }
  6050.  
  6051.  
  6052. # Load an XML object from the library
  6053. # You specify the relative file path (to .../db/), less the .xml on the end.
  6054. sub _get_object_xml {
  6055.     my ($this, $file, $quiet) = @_;
  6056.  
  6057.     open XML, "$libdir/db/$file.xml"
  6058.     or do { warn "Cannot open file $libdir/db/$file.xml\n"
  6059.             if !$quiet;
  6060.         return undef; };
  6061.     my $xml = join('', (<XML>));
  6062.     close XML;
  6063.  
  6064.     return $xml;
  6065. }
  6066.  
  6067. # Write an XML object from the library
  6068. # You specify the relative file path (to .../db/), less the .xml on the end.
  6069. sub _set_object_xml {
  6070.     my ($this, $file, $stuff, $cache) = @_;
  6071.  
  6072.     my $dir = "$libdir/db";
  6073.     my $xfile = "$dir/$file.xml";
  6074.     umask 0002;
  6075.     open XML, ">$xfile.$$"
  6076.     or do { warn "Cannot write file $xfile.$$\n";
  6077.         return undef; };
  6078.     print XML $stuff;
  6079.     close XML;
  6080.     rename "$xfile.$$", $xfile
  6081.     or die "Cannot rename $xfile.$$ to $xfile\n";
  6082.  
  6083.     return 1;
  6084. }
  6085.  
  6086. # Get a list of XML filenames from a library directory.  These could then be
  6087. # read with _get_object_xml.
  6088. sub _get_xml_filelist {
  6089.     my ($this, $dir) = @_;
  6090.  
  6091.     if (!defined($this->{"names-$dir"})) {
  6092.     opendir DRV, "$libdir/db/$dir"
  6093.         or die 'Cannot find source db for $dir\n';
  6094.     my $driverfile;
  6095.     while($driverfile = readdir(DRV)) {
  6096.         next if ($driverfile !~ m!^(.+)\.xml$!);
  6097.         push(@{$this->{"names-$dir"}}, $1);
  6098.     }
  6099.     closedir(DRV);
  6100.     }
  6101.  
  6102.     return @{$this->{"names-$dir"}};
  6103. }
  6104.  
  6105.  
  6106. # Return a Perl structure in eval-able ascii format
  6107. sub getascii {
  6108.     my ($this) = $_[0];
  6109.     if (! $this->{'dat'}) {
  6110.     $this->getdat();
  6111.     }
  6112.     
  6113.     local $Data::Dumper::Purity=1;
  6114.     local $Data::Dumper::Indent=1;
  6115.  
  6116.     # Encase data for inclusion in PPD file
  6117.     return Dumper($this->{'dat'});
  6118. }
  6119.  
  6120. # Return list of printer makes
  6121. sub get_makes {
  6122.     my ($this) = @_;
  6123.  
  6124.     my @makes;
  6125.     my %seenmakes;
  6126.     my $p;
  6127.     for $p (@{$this->get_overview()}) {
  6128.     my $make = $p->{'make'};
  6129.     push (@makes, $make) 
  6130.         if ! $seenmakes{$make}++;
  6131.     }
  6132.     
  6133.     return @makes;
  6134.     
  6135. }
  6136.  
  6137. # get a list of model names from a make
  6138. sub get_models_by_make {
  6139.     my ($this, $wantmake) = @_;
  6140.  
  6141.     my $over = $this->get_overview();
  6142.  
  6143.     my @models;
  6144.     my $p;
  6145.     for $p (@{$over}) {
  6146.     push (@models, $p->{'model'}) 
  6147.         if ($wantmake eq $p->{'make'});
  6148.     }
  6149.  
  6150.     return @models;
  6151. }
  6152.  
  6153. # get a printer id from a make/model
  6154. sub get_printer_from_make_model {
  6155.     my ($this, $wantmake, $wantmodel) = @_;
  6156.  
  6157.     my $over = $this->get_overview();
  6158.     my $p;
  6159.     for $p (@{$over}) {
  6160.     return $p->{'id'} if ($p->{'make'} eq $wantmake
  6161.                   and $p->{'model'} eq $wantmodel);
  6162.     }
  6163.  
  6164.     return undef;
  6165. }
  6166.  
  6167. sub get_javascript2 {
  6168.  
  6169.     my ($this, $models, $oids) = @_;
  6170.  
  6171.     my @swit;
  6172.     my $mak;
  6173.     my $else = "";
  6174.     my @makes;
  6175.     my %modelhash;
  6176.     my %oidhash;
  6177.     if ($models) {
  6178.     %modelhash = %{$models};
  6179.     @makes = sort {normalizename($a) cmp normalizename($b) } (keys %modelhash);
  6180.     } else {
  6181.     @makes = sort {normalizename($a) cmp normalizename($b) } ($this->get_makes());
  6182.     }
  6183.     if ($oids) {
  6184.     %oidhash = %{$oids};
  6185.     }
  6186.     for $mak (@makes) {
  6187.     push (@swit,
  6188.           " $else if (make == \"$mak\") {\n");
  6189.  
  6190.     my $ct = 0;
  6191.  
  6192.     my @makemodels;
  6193.     if ($models) {
  6194.         @makemodels = @{$modelhash{$mak}};
  6195.     } else {
  6196.         @makemodels = ($this->get_models_by_make($mak));
  6197.     }
  6198.     my $mod;
  6199.     for $mod (sort {normalizename($a) cmp normalizename($b) } 
  6200.           @makemodels) {
  6201.         
  6202.         my $p;
  6203.         $p = $this->get_printer_from_make_model($mak, $mod);
  6204.         if (defined($p)) {
  6205.         push (@swit,
  6206.               "      o[i++]=new Option(\"$mod\", \"$p\");\n");
  6207.         $ct++;
  6208.         } else {
  6209.         my $oid;
  6210.         if ($oids) {
  6211.             $oid = $oidhash{$mak}{$mod};
  6212.         } else {
  6213.             $oid = "$mak-$mod";
  6214.             $oid =~ s/ /_/g;
  6215.             $oid =~ s/\+/plus/g;
  6216.             $oid =~ s/[^A-Za-z0-9_\-]//g;
  6217.             $oid =~ s/__+/_/g;
  6218.             $oid =~ s/_$//;
  6219.         }
  6220.         push (@swit,
  6221.               "      o[i++]=new Option(\"$mod\", \"$oid\");\n");
  6222.         $ct++;
  6223.         }
  6224.     }
  6225.  
  6226.     if (!$ct) {
  6227.         push(@swit,
  6228.          "      o[i++]=new Option(\"No Printers\", \"0\");\n");
  6229.     }
  6230.  
  6231.     push (@swit,
  6232.           "    }");
  6233.     $else = "else";
  6234.     }
  6235.  
  6236.     my $switch = join('',@swit);
  6237.  
  6238.     my $javascript = '
  6239.        function reflectMake(makeselector, modelselector) {
  6240.      //
  6241.      // This function is called when makeselector changes
  6242.      // by an onchange thingy on the makeselector.
  6243.      //
  6244.  
  6245.      // Get the value of the OPTION that just changed
  6246.      selected_value=makeselector.options[makeselector.selectedIndex].value;
  6247.      // Get the text of the OPTION that just changed
  6248.      make=makeselector.options[makeselector.selectedIndex].text;
  6249.  
  6250.      o = new Array;
  6251.      i=0;
  6252.  
  6253.      ' . $switch . '    if (i==0) {
  6254.        alert("Error: that dropdown should do something, but it doesnt");
  6255.      } else {
  6256.        modelselector.length=o.length;
  6257.        for (i=0; i < o.length; i++) {
  6258.          modelselector.options[i]=o[i];
  6259.        }
  6260.        modelselector.options[0].selected=true;
  6261.      }
  6262.  
  6263.        }
  6264.      ';
  6265.  
  6266.     return $javascript;
  6267. }
  6268.  
  6269.  
  6270.  
  6271.  
  6272. # Modify comments text to contain only what it should:
  6273. #
  6274. # <a>, <p>, <br> (<br> -> <p>)
  6275. #
  6276. sub comment_filter {
  6277.     my ($text) = @_;
  6278.  
  6279.     my $fake = ("INSERTFIXEDTHINGHERE" . sprintf("%06x", rand(1000000)));
  6280.     my %replacements;
  6281.     my $num = 1;
  6282.  
  6283.     # extract all the A href tags
  6284.     my $replace = "ANCHOR$fake$num";
  6285.     while ($text =~ 
  6286.        s!(<\s*a\s+href\s*=\s*['"]([^'"]+)['"]\s*>)!$replace!i) {
  6287.     $replacements{$replace} = $1;
  6288.     $num++;
  6289.     $replace = "ANCHOR$fake$num";
  6290.     }
  6291.  
  6292.     # extract all the A tail tags
  6293.     $replace = "ANCHORTAIL$fake$num";
  6294.     while ($text =~ 
  6295.        s!(<\s*/\s*a\s*>)!$replace!i) {
  6296.     $replacements{$replace} = $1;
  6297.     $num++;
  6298.     $replace = "ANCHOR$fake$num";
  6299.     }
  6300.  
  6301.     # extract all the P tags
  6302.     $replace = "PARA$fake$num";
  6303.     while ($text =~ 
  6304.        s!(<\s*p\s*>)!$replace!i) {
  6305.  
  6306.     $replacements{$replace} = $1;
  6307.     $num++;
  6308.     $replace = "PARA$fake$num";
  6309.     }
  6310.  
  6311.     # extract all the BR tags
  6312.     $replace = "PARA$fake$num";
  6313.     while ($text =~ 
  6314.        s!(<\s*br\s*>)!$replace!i) {
  6315.  
  6316.     $replacements{$replace} = $1;
  6317.     $num++;
  6318.     $replace = "PARA$fake$num";
  6319.     }
  6320.  
  6321.     # Now it's just clean text; remove all tags and &foo;s
  6322.     $text =~ s!<[^>]+>! !g;
  6323.     $text =~ s!&!&!g;
  6324.     $text =~ s!<!<!g;
  6325.     $text =~ s!>!>!g;
  6326.     $text =~ s!&[^;]+?;! !g;
  6327.  
  6328.     # Now rewrite into our teeny-html subset
  6329.     $text =~ s!&!&!g;
  6330.     $text =~ s!<!<!g;
  6331.     $text =~ s!>!>!g;
  6332.  
  6333.     # And reinsert the few things we wanted to preserve
  6334.     for (keys(%replacements)) {
  6335.     my ($k, $r) = ($_, $replacements{$_});
  6336.     $text =~ s!$k!$r!;
  6337.     }
  6338.  
  6339. #    print STDERR "$text";
  6340.  
  6341.     return $text;
  6342. }
  6343.  
  6344. 1;
  6345.